ahuang11 commited on
Commit
b9a0f21
1 Parent(s): 6b9c4b0

Upload 52 files

Browse files
Files changed (48) hide show
  1. hvplot_docs/01-Annotating_Data.md +135 -0
  2. hvplot_docs/05-Dimensioned_Containers.md +162 -0
  3. hvplot_docs/06-Building_Composite_Objects.md +191 -0
  4. hvplot_docs/07-Live_Data.md +354 -0
  5. hvplot_docs/08-Tabular_Datasets.md +297 -0
  6. hvplot_docs/09-Gridded_Datasets.md +294 -0
  7. hvplot_docs/10-Indexing_and_Selecting_Data.md +268 -0
  8. hvplot_docs/11-Transforming_Elements.md +336 -0
  9. hvplot_docs/12-Responding_to_Events.md +511 -0
  10. hvplot_docs/13-Custom_Interactivity.md +222 -0
  11. hvplot_docs/14-Data_Pipelines.md +123 -0
  12. hvplot_docs/15-Large_Data.md +576 -0
  13. hvplot_docs/16-Streaming_Data.md +338 -0
  14. hvplot_docs/17-Dashboards.md +159 -0
  15. hvplot_docs/Annotators.md +229 -0
  16. hvplot_docs/Colormaps.md +234 -0
  17. hvplot_docs/Continuous_Coordinates.md +201 -0
  18. hvplot_docs/Customization.md +185 -0
  19. hvplot_docs/Customizing_Plots.md +439 -0
  20. hvplot_docs/Deploying_Bokeh_Apps.md +553 -0
  21. hvplot_docs/Explorer.md +123 -0
  22. hvplot_docs/Exporting_and_Archiving.md +246 -0
  23. hvplot_docs/Geographic_Data.md +152 -0
  24. hvplot_docs/Geometry_Data.md +134 -0
  25. hvplot_docs/Gridded_Data.md +129 -0
  26. hvplot_docs/Installing_and_Configuring.md +107 -0
  27. hvplot_docs/Integrations.md +251 -0
  28. hvplot_docs/Interactive.md +238 -0
  29. hvplot_docs/Introduction.md +115 -0
  30. hvplot_docs/Linked_Brushing.md +0 -0
  31. hvplot_docs/Linking_Plots.md +159 -0
  32. hvplot_docs/NetworkX.md +552 -0
  33. hvplot_docs/Network_Graphs.md +249 -0
  34. hvplot_docs/Notebook_Magics.md +156 -0
  35. hvplot_docs/Pandas_API.md +628 -0
  36. hvplot_docs/Plots_and_Renderers.md +343 -0
  37. hvplot_docs/Plotting.md +325 -0
  38. hvplot_docs/Plotting_Extensions.md +109 -0
  39. hvplot_docs/Plotting_with_Bokeh.md +549 -0
  40. hvplot_docs/Plotting_with_Matplotlib.md +341 -0
  41. hvplot_docs/Plotting_with_Plotly.md +340 -0
  42. hvplot_docs/Statistical_Plots.md +68 -0
  43. hvplot_docs/Streaming.md +139 -0
  44. hvplot_docs/Subplots.md +70 -0
  45. hvplot_docs/Timeseries_Data.md +103 -0
  46. hvplot_docs/Viewing.md +90 -0
  47. hvplot_docs/Widgets.md +92 -0
  48. hvplot_docs/plot.html +66 -0
hvplot_docs/01-Annotating_Data.md ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Annotating Your Data
2
+
3
+
4
+ ```python
5
+ import holoviews as hv
6
+ hv.extension('bokeh')
7
+ ```
8
+
9
+ As introduced in the [Getting Started guide](../getting_started/1-Introduction.ipynb), HoloViews relies heavily on semantic *annotations*, i.e., metadata you declare that lets HoloViews interpret what your data represents. With these annotations, HoloViews can perform complex tasks like visualization automatically.
10
+
11
+ There are three main kinds of annotation that can be associated with each element:
12
+ 1. **Type**, used to declare the sort of data you have, which is required before it can be visualized,
13
+ 2. **Dimensions**, used to specify the abstract space in which the data resides, allowing axis labeling and indexing, and
14
+ 3. **Group/Label**, used to declare a meaningful category and human-readable description of the element, allowing plot labeling and selecting related sets of elements.
15
+
16
+ This user guide explains each of these three types of annotation, describing why you would need or want to use them.
17
+
18
+ ## 1. Specifying element type
19
+
20
+ Basic Python data structures like dataframes, arrays, lists, and dictionaries can be used to represent an infinite variety of different types of data, and thus they cannot be visualized as any particular type of graphical representation without some additional information from the user that says what sort of data it is meant to be. The user can declare this information by selecting a suitable HoloViews element type from the many different ones available (see the [Reference Gallery](http://holoviews.org/reference/index.html)).
21
+
22
+ For instance, let's say you have two lists of numbers:
23
+
24
+
25
+ ```python
26
+ xs = range(-10,11)
27
+ ys = [100-x**2 for x in xs]
28
+ ```
29
+
30
+ As far as Python is concerned, ``xs`` and ``ys`` are just two arbitrary lists, which could represent nearly anything imaginable. But we as humans can see that each of the ``ys`` is a value computed from one of the ``xs`` by evaluating the function $y=100-x^2$. We can convey some of that information to HoloViews by choosing a ``Curve`` element type, which is a convenient shorthand for "a discrete set of real-valued samples from a continuous function of one real-valued variable":
31
+
32
+
33
+ ```python
34
+ curve = hv.Curve((xs, ys))
35
+ curve
36
+ ```
37
+
38
+ As you can see, declaring the element type is the only *required* bit of annotation, instantly making your data visualizable. However, this initial visualization relies on various defaults that may not be appropriate for your data, and you can override these defaults by declaring additional annotations as described below.
39
+
40
+ ## 2. Specifying element dimensionality
41
+
42
+ Each element type can process a certain number and type of *dimensions*, i.e., ways in which the data can vary. For instance, the ``Curve`` object above has two dimensions, $x$ and $y$. If you look at how we generated the data, you can see that these two dimensions are semantically different -- we chose an arbitrary set of values for the ``xs``, and then calculated a corresponding value to make each of the ``ys``. In mathematical terms, $x$ is thus an independent variable (selected by the creator of the data), and $y$ is a dependent variable (typically measured or calculated from the independent variable(s)).
43
+
44
+ HoloViews elements call these two different types of variables *key dimensions* (``kdims``) and *value dimensions* (``vdims``). The *key dimensions* are the dimensions you can index *by* to get the values corresponding to the *value* dimensions. You can learn more about indexing data in the later [Indexing and Selecting Data](./10-Indexing_and_Selecting_Data.ipynb) user guide.
45
+
46
+ Different elements have different numbers of required key dimensions and value dimensions. For instance, a ``Curve`` always has one key dimension and one value dimension. As we did not explicitly specify anything regarding dimensions when declaring the curve above, the ``kdims`` and ``vidms`` use their default names 'x' and 'y':
47
+
48
+
49
+ ```python
50
+ "Object 'curve' has kdims {kdims} and vdims {vdims}".format(kdims=curve.kdims, vdims=curve.vdims)
51
+ ```
52
+
53
+ The easiest way to override the default dimension names is to provide strings for the dimensions, where the second argument in the Element constructor will always be the ``kdims``, and the third will always be the ``vdims``:
54
+
55
+
56
+ ```python
57
+ trajectory = hv.Curve((xs, ys), 'distance', 'height')
58
+ trajectory
59
+ ```
60
+
61
+
62
+ ```python
63
+ "Object 'trajectory' has kdims {kdims} and vdims {vdims} ".format(kdims=trajectory.kdims, vdims=trajectory.vdims)
64
+ ```
65
+
66
+ We can see that the strings we provided have been 'promoted' to dimension objects. The ``kdims`` and ``vdims`` *always* contain instances of the ``Dimension`` class, described in the following section. Here, the immediate effect is to use the new names for the displayed axis labels.
67
+
68
+ ### Dimension parameters
69
+
70
+ ``Dimensions`` are not just names, they are rich objects with numerous parameters that can be used to describe the space in which the data resides. Only two of these are considered *core* parameters that uniquely identify the dimension object; the rest are auxiliary metadata. The most important parameters are:
71
+
72
+ <br>
73
+ <dl class="dl-horizontal">
74
+ <dt>name</dt><dd>(core) A concise name for the dimension, which for convenient usage as a keyword argument should usually be a legal Python identifier.</dd>
75
+ <dt>label <dd>(core) A optional longer description of the dimension, which is convenient if you want the displayed label to contain arbitrary spaces, symbols, or unicode.</dd>
76
+ <dt>range <dd>The minimum and maximum allowable values for the dimension, for error checking and generating widgets when needed.</dd>
77
+ <dt>soft_range <dd>Suggested minimum and maximum values within the allowed range, used to specify a useful portion of the range for widgets and animations.</dd>
78
+ <dt>step <dd>Suggested interval for sampling a continuous range, if needed for a widget or animation.</dd>
79
+ <dt>unit <dd>The name of the unit to be associated with the dimension, if any, for labelling.</dd>
80
+ <dt>values <dd>Explicit list of allowed dimension values, for error checking, widgets, and animations.</dd>
81
+ </dl>
82
+
83
+
84
+ For the full list of parameters, you can call ``hv.help(hv.Dimension)``.
85
+
86
+ Similar to how you can just use a string if all you want to specify is the name, you can provide a ``(name,label)`` tuple if you want to specify the ``name`` and the ``label`` to ``kdims`` and ``vdims`` without building an explicit ``Dimension``:
87
+
88
+
89
+ ```python
90
+ wo_unit = hv.Curve((xs, ys),
91
+ ('distance','Horizontal distance'),
92
+ ('height','Height above sea level'))
93
+
94
+ distance = hv.Dimension('distance', label='Horizontal distance', unit='m')
95
+ height = hv.Dimension(('height','Height above sea level'), unit='m')
96
+ with_unit = hv.Curve((xs, ys), distance, height)
97
+
98
+ # (using + to compose elements is described in the next guide)
99
+ wo_unit + with_unit
100
+ ```
101
+
102
+ Note that after supplying the longer labels, you can still use the short name to specify the dimension in keyword arguments. For instance, try using ``with_unit.select(distance=(5,8))`` in the cell above.
103
+
104
+ ### Setting properties with redim
105
+
106
+ Declaring dimension objects with appropriate parameters can be awkward and verbose if you only want to set a few specific parameters. You can often avoid declaring explicit dimension objects using the ``redim`` method, which returns a *clone* of the element: the same data, wrapped in a new instance of the same element type with the new dimension settings.
107
+
108
+ Let's use ``redim`` to swap out the 'height' dimension for an 'altitude' dimension:
109
+
110
+
111
+ ```python
112
+ renamed_height = trajectory.redim(height='altitude')
113
+ renamed_height
114
+ ```
115
+
116
+ The ``redim`` "method" is actually a utility that can be used to set any of the dimension parameters, such as the label, unit, range, or values. For instance, the label can be updated on an existing object by specifying the dimension name and then the new value for that parameter:
117
+
118
+
119
+ ```python
120
+ renamed_height.redim.label(altitude='Altitude above sea-level', distance='Horizontal distance')
121
+ ```
122
+
123
+ ## 3. Organizing your elements with groups and labels
124
+
125
+ A complex visualization you build with HoloViews may include many instances of the same element type, each built from different bits of data and potentially representing categorically distinct types of information to you. To help you keep track of these distinctions when you need to, HoloViews provides a ``group`` parameter you can use to declare semantically distinct categories for elements, and a ``label`` parameter you can use to identify which specific item the element represents within that category:
126
+
127
+
128
+ ```python
129
+ low_ys = [25-(0.5*el)**2 for el in xs]
130
+ shallow = hv.Curve((xs, low_ys), group='Trajectory', label='Shallow')
131
+ medium = hv.Curve((xs, ys), group='Trajectory', label='Medium')
132
+ shallow + medium
133
+ ```
134
+
135
+ As you can see, the ``group`` and ``label`` information will be used to generate sensible titles, here indicating that both sets of data represent trajectories, and that there are two different specific trajectories being shown. Once the group and/or label have been specified, they can be used for [Applying Customization](./03-Applying_Customizations.ipynb) (e.g. to make all trajectories have the same line width and style, or to customize one particular plot out of many of the same type). The group and label are also used for indexing, as we will see in the following [Composing_Elements](./02-Composing_Elements.ipynb) guide.
hvplot_docs/05-Dimensioned_Containers.md ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dimensioned Containers
2
+
3
+ So far we've seen how to [wrap data in elements](./01-Annotating_Data.ipynb) and [compose](./02-Composing_Elements.ipynb) those Elements into Overlays and Layout. In this guide will see how we can use containers to hold Elements and declare parameter spaces to explore multi-dimensional parameter spaces visually. These containers allow faceting your data by one or more variables and exploring the resulting parameter space with widgets, positioning plots on a grid or simply laying them out consecutively. Here we will introduce the ``HoloMap``, ``NdOverlay``, ``NdLayout`` and ``GridSpace``, which make all this possible.
4
+
5
+
6
+ ```python
7
+ import numpy as np
8
+ import holoviews as hv
9
+
10
+ from holoviews import opts
11
+
12
+ hv.extension('bokeh')
13
+
14
+ opts.defaults(opts.Curve(line_width=1))
15
+ ```
16
+
17
+ ### Declaring n-dimensional collections
18
+
19
+ Python users will be familiar with dictionaries as a way to collect data together in a conveniently accessible manner. Unlike NumPy arrays, dictionaries are sparse and do not have to be declared with a fixed size. The dimensioned types are therefore closely modeled on dictionaries but support n-dimensional keys.
20
+
21
+ Therefore they allow you to express a mapping between a multi-variable key and other viewable objects, letting you structure your multi-dimensional data to facilitate exploration. The key here can represent anything from an ID, a filename, a parameter controlling some custom processing to some category representing a subset of your data.
22
+
23
+ As a simple example we will use a function which varies with multiple parameters, in this case a function which generates a simple frequency modulation signal, with two main parameters a carrier frequency and a modulation frequency (see [Wikipedia](https://en.wikipedia.org/wiki/Frequency_modulation)). All we have to know here is that the function generates a ``Curve`` based on the parameters that we give it.
24
+
25
+
26
+ ```python
27
+ def fm_modulation(f_carrier=220, f_mod=220, mod_index=1, length=0.1, sampleRate=2000):
28
+ sampleInc = 1.0/sampleRate
29
+ x = np.arange(0,length, sampleInc)
30
+ y = np.sin(2*np.pi*f_carrier*x + mod_index*np.sin(2*np.pi*f_mod*x))
31
+ return hv.Curve((x, y), 'Time', 'Amplitude')
32
+ ```
33
+
34
+ Next we have to declare the parameter space we want to explore. Combinatorial parameter spaces can quickly get very large so here we will declare just 5 carrier frequencies and 5 modulation frequencies. If we have many more parameters or a larger set of values we could instead use a ``DynamicMap``, which does lazy evaluation and is introduced in the next section on [Live Data](./07-Live_Data.ipynb).
35
+
36
+ However when working with relatively small datasets a ``HoloMap`` is preferred as ``HoloMap``s can be exported to static HTML. To declare the data we use a dictionary comprehension to compute an FM modulation curve for each combination of carrier and modulation frequencies and then pass that dictionary to a newly declared ``HoloMap``, which declares our two parameters as key dimensions. If we want to customize the starting value of the widgets a ``default`` value may be supplied on the ``Dimension`` objects:
37
+
38
+
39
+ ```python
40
+ f_carrier = np.linspace(20, 60, 3)
41
+ f_mod = np.linspace(20, 100, 5)
42
+
43
+ curve_dict = {(fc, fm): fm_modulation(fc, fm) for fc in f_carrier for fm in f_mod}
44
+
45
+ kdims = [hv.Dimension(('f_carrier', 'Carrier frequency'), default=40),
46
+ hv.Dimension(('f_mod', 'Modulation frequency'), default=60)]
47
+ holomap = hv.HoloMap(curve_dict, kdims=kdims)
48
+ holomap.opts(opts.Curve(width=600))
49
+ ```
50
+
51
+ Any numeric key dimension values will automatically generate sliders while any non-numeric value will present you with a dropdown widget to select between the values. A HoloMap is therefore an easy way to quickly explore a parameter space. Since only one ``Curve`` is on the screen at the same time it is difficult to compare the data, we can however easily facet the data in other ways.
52
+
53
+ ## Casting between n-dimensional containers
54
+
55
+ Exploring a parameter space using widgets is one of the most flexible approaches but as we noted above it also makes it difficult to compare between different parameter values. HoloViews therefore supplies several container objects, which behave similarly to a ``HoloMap`` but have a different visual representation:
56
+
57
+ * NdOverlay - An n-dimensional container which overlays the elements
58
+ * NdLayout - An n-dimensional container which displays the data in separate plot axes and adds titles for each value
59
+ * GridSpace - A 1D or 2D container which lays out up to two dimensions on a grid.
60
+
61
+ Since all these classes share the same baseclass we can trivially cast between them, e.g. we can cast the ``HoloMap`` to a ``GridSpace``.
62
+
63
+
64
+ ```python
65
+ grid = hv.GridSpace(holomap)
66
+ grid.opts(
67
+ opts.GridSpace(plot_size=75),
68
+ opts.Curve(width=100))
69
+ ```
70
+
71
+ Similarly we can select just a few values and lay the data out in an ``NdLayout``:
72
+
73
+
74
+ ```python
75
+ ndlayout = hv.NdLayout(grid[20, 20:81])
76
+ ndlayout.opts(opts.Curve(width=500, height=200)).cols(2)
77
+ ```
78
+
79
+ ## Faceting by dimension
80
+
81
+ Casting between container types as we did above allows us to facet all dimensions but often it is more desirable to facet a specific dimension in some way. We may for example want to overlay the carrier frequencies, while still having a slider vary the modulation frequencies. For this purpose ``HoloMap`` and ``DynamicMap`` have ``.overlay``, ``.grid`` and ``.layout`` methods, which accept one or more dimensions as input, e.g. here we overlay the carrier frequencies:
82
+
83
+
84
+ ```python
85
+ holomap.overlay('f_carrier').opts(width=600)
86
+ ```
87
+
88
+ We can chain these faceting operations but we have to be careful about the order. We should always ``overlay`` first and only then use ``.grid`` or ``.layout`` . To better understand why that is, look at the [Building Composite objects](./06-Building_Composite_Objects.ipynb) guide, for now it suffices to say that objects are built to nest in a specific order. Here we will ``overlay`` the carrier frequencies and ``grid`` the modulation frequency:
89
+
90
+
91
+ ```python
92
+ gridspace = holomap.overlay('f_carrier').grid('f_mod')
93
+ gridspace.opts(opts.Curve(width=200,height=200))
94
+ ```
95
+
96
+ This ability to facet data in different ways becomes incredibly useful when working with [tabular](./08-Tabular_Datasets.ipynb) and [gridded](./09-Gridded_Datasets.ipynb) datasets, which vary by multiple dimensions. By faceting the data by one or more dimensions and utilizing the wide range of elements we can quickly explore huge and complex datasets particularly when working with using ``DynamicMap``, which allows you to define dynamic and lazy [data processing pipelines](./14-Data_Pipelines.ipynb), which visualize themselves and allow you to build complex interactive dashboards.
97
+
98
+ ## Methods
99
+
100
+ ### Selecting by value
101
+
102
+ Just like ``Elements`` dimensioned containers can be sliced and indexed by value using the regular indexing syntax and the ``select`` method. As a simple example we can index the carrier frequency 20 and modulation frequency 40 using both the indexing and select syntax:
103
+
104
+
105
+ ```python
106
+ layout = holomap[20, 40] + holomap.select(f_carrier=20, f_mod=40)
107
+ layout.opts(opts.Curve(width=400))
108
+ ```
109
+
110
+ For more detail on indexing both Elements and containers see the [Indexing and Selecting user guide](./10-Indexing_and_Selecting_Data.ipynb).
111
+
112
+ ### Collapsing an n-dimensional container
113
+
114
+ If we inspect one of the containers we created in the examples above we can clearly see the structure and the dimensionality of each container and the underlying ``Curve`` elements:
115
+
116
+
117
+ ```python
118
+ print(grid)
119
+ ```
120
+
121
+ Since this simply represents a multi-dimensional parameter space we can collapse this datastructure into a single `Dataset` containing columns for each of the dimensions we have declared:
122
+
123
+
124
+ ```python
125
+ ds = grid.collapse()
126
+ ds.data.head()
127
+ ```
128
+
129
+ A ``Dataset`` is an element type which does not itself have a visual representation and may contain any type of data supported by HoloViews interfaces.
130
+
131
+ ### Reindexing containers
132
+
133
+ Something we might want to do quite frequently is to change the order of dimensions in a dimensioned container. Let us inspect the key dimensions on our HoloMap from above:
134
+
135
+
136
+ ```python
137
+ holomap.kdims
138
+ ```
139
+
140
+ Using the ``reindex`` method we can easily change the order of dimensions:
141
+
142
+
143
+ ```python
144
+ reindexed = holomap.reindex(['f_mod', 'f_carrier'])
145
+ reindexed.kdims
146
+ ```
147
+
148
+ ### Add dimensions to a container
149
+
150
+ Another common operation is adding a new dimension to a HoloMap, which can be useful when you want to merge two HoloMaps which vary by yet another dimension. The ``add_dimension`` method takes the new dimension name, the position in the list of key dimensions and the actual value as arguments:
151
+
152
+
153
+ ```python
154
+ new_holomap = holomap.add_dimension('New dimension', dim_pos=0, dim_val=1)
155
+ new_holomap.kdims
156
+ ```
157
+
158
+ As you can see the 'New dimension' was added at position zero.
159
+
160
+ ## Onwards
161
+
162
+ As we have seen Dimensioned containers make it easy to explore multi-dimensional datasets by mapping the dimensions onto visual layers (`NdOverlay`), 2D grid axes (`GridSpace`), widgets (`HoloMap`) or an arbitrary layout (`NdLayout`). In the [next section](06-Building_Composite_Objects.ipynb) we will discover how to construct composite objects of heterogeneous data.
hvplot_docs/06-Building_Composite_Objects.md ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Building Composite Objects
2
+
3
+ The [reference gallery](http://holoviews.org/reference/index.html) shows examples of each of the container types in HoloViews. As you work through this guide, it will be useful to look at the description of each type there.
4
+
5
+ This guide shows you how to combine the various container types, in order to build data structures that can contain all of the data that you want to visualize or analyze, in an extremely flexible way. For instance, you may have a large set of measurements of different types of data (numerical, image, textual notations, etc.) from different experiments done on different days, with various different parameter values associated with each one. HoloViews can store all of this data together, which will allow you to select just the right bit of data "on the fly" for any particular analysis or visualization, by indexing, slicing, selecting, and sampling in this data structure.
6
+
7
+ ## Nesting hierarchy <a id='NestingHierarchy'></a>
8
+
9
+ To illustrate the full functionality provided, we will create an example of the maximally nested object structure currently possible with HoloViews:
10
+
11
+
12
+ ```python
13
+ import numpy as np
14
+ import holoviews as hv
15
+ hv.extension('bokeh')
16
+ np.random.seed(10)
17
+
18
+ def sine_curve(phase, freq, amp, power, samples=102):
19
+ xvals = [0.1* i for i in range(samples)]
20
+ return [(x, amp*np.sin(phase+freq*x)**power) for x in xvals]
21
+
22
+ phases = [0, np.pi/2, np.pi, 3*np.pi/2]
23
+ powers = [1,2,3]
24
+ amplitudes = [0.5,0.75, 1.0]
25
+ frequencies = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75]
26
+
27
+
28
+ gridspace = hv.GridSpace(kdims=['Amplitude', 'Power'], group='Parameters', label='Sines')
29
+
30
+ for power in powers:
31
+ for amplitude in amplitudes:
32
+ holomap = hv.HoloMap(kdims='Frequency')
33
+ for frequency in frequencies:
34
+ sines = {phase : hv.Curve(sine_curve(phase, frequency, amplitude, power))
35
+ for phase in phases}
36
+ ndoverlay = hv.NdOverlay(sines, kdims='Phase').relabel(group='Phases',
37
+ label='Sines', depth=1)
38
+ overlay = ndoverlay * hv.Points([(i,0) for i in range(0,10)], group='Markers', label='Dots')
39
+ holomap[frequency] = overlay
40
+ gridspace[amplitude, power] = holomap
41
+
42
+ penguins = hv.RGB.load_image('../reference/elements/assets/penguins.png').relabel(group="Family", label="Penguin")
43
+
44
+ layout = gridspace + penguins.opts(axiswise=True)
45
+ ```
46
+
47
+ This code produces what looks like a relatively simple animation of two side-by-side figures, but is actually a deeply nested data structure:
48
+
49
+
50
+ ```python
51
+ layout
52
+ ```
53
+
54
+ To help us understand this structure, here is a schematic for us to refer to as we unpack this object, level by level:
55
+
56
+ <center><img src="https://assets.holoviews.org/nesting-diagram.png"></center>
57
+
58
+ Everything that is *displayable* in HoloViews has this same basic hierarchical structure, although any of the levels can be omitted in simpler cases, and many different Element types (not containers) can be substituted for any other.
59
+
60
+ Since HoloViews 1.3.0, you are allowed to build data-structures that violate this hierarchy (e.g., you can put ``Layout`` objects into ``HoloMaps``) but the resulting object cannot be displayed. Instead, you will be prompted with a message to call the ``collate`` method. Using the ``collate`` method will allow you to generate the appropriate object that correctly obeys the hierarchy shown above, so that it can be displayed.
61
+
62
+ As shown in the diagram, there are three different types of container involved:
63
+
64
+ - Basic Element: elementary HoloViews object containing raw data in an external format like Numpy or pandas.
65
+ - Homogeneous container (UniformNdMapping): collections of Elements or other HoloViews components that are all the same type. These are indexed using array-style key access with values sorted along some dimension(s), e.g. ``[0.50]`` or ``["a",7.6]``.
66
+ - Heterogeneous container (AttrTree): collections of data of different types, e.g. different types of Element. These are accessed by categories using attributes, e.g. ``.Parameters.Sines``, which does not assume any ordering of a dimension.
67
+
68
+ We will now go through each of the containers of these different types, at each level.
69
+
70
+ ### ``Layout`` Level
71
+
72
+ Above, we have already viewed the highest level of our data structure as a Layout. Here is the representation of the entire Layout object, which reflects all the levels shown in the diagram:
73
+
74
+
75
+ ```python
76
+ print(layout)
77
+ ```
78
+
79
+ In the examples below, we will unpack this data structure using attribute access (explained in the [Annotating Data](./01-Annotating_Data.ipynb) user guide) as well as indexing and slicing (explained in the [Indexing and Selecting Data](./10-Indexing_and_Selecting_Data.ipynb) user guide).
80
+
81
+ ### ``GridSpace`` Level
82
+
83
+ Elements within a ``Layout``, such as the ``GridSpace`` in this example, are reached via attribute access:
84
+
85
+
86
+ ```python
87
+ layout.Parameters.Sines
88
+ ```
89
+
90
+ ### ``HoloMap`` Level
91
+
92
+ This ``GridSpace`` consists of nine ``HoloMap``s arranged in a two-dimensional space. Let's now select one of these ``HoloMap`` objects by indexing to retrieve the one at [Amplitude,Power] ``[0.5,1.0]``, i.e. the lowest amplitude and power:
93
+
94
+
95
+ ```python
96
+ layout.Parameters.Sines[0.5, 1]
97
+ ```
98
+
99
+ As shown in the schematic above, a ``HoloMap`` contains many elements with associated keys. In this example, these keys are indexed with a dimension ``Frequency``, which is why the ``Frequency`` varies when you play the animation here.
100
+
101
+ ### ``Overlay`` Level
102
+
103
+ The printed representation showed us that the ``HoloMap`` is composed of ``Overlay`` objects, six in this case (giving six frames to the animation above). Let us access one of these elements, i.e. one frame of the animation above, by indexing to retrieve an ``Overlay`` associated with the key with a ``Frequency`` of *1.0*:
104
+
105
+
106
+ ```python
107
+ layout.Parameters.Sines[0.5, 1][1.0]
108
+ ```
109
+
110
+ ### NdOverlay Level
111
+
112
+ As the representation shows, the ``Overlay`` contains a ``Points`` object and an ``NdOverlay`` object. We can access either one of these using the attribute access supported by ``Overlay``:
113
+
114
+
115
+ ```python
116
+ (layout.Parameters.Sines[0.5, 1][1].Phases.Sines +
117
+ layout.Parameters.Sines[0.5, 1][1].Markers.Dots)
118
+ ```
119
+
120
+ ### ``Curve`` Level
121
+
122
+ The ``NdOverlay`` is so named because it is an overlay of items indexed by dimensions, unlike the regular attribute-access overlay types. In this case it is indexed by ``Phase``, with four values. If we index to select one of these values, we will get an individual ``Curve``, e.g. the one with zero phase:
123
+
124
+
125
+ ```python
126
+ l=layout.Parameters.Sines[0.5, 1][1].Phases.Sines[0.0]
127
+ l
128
+ ```
129
+
130
+
131
+ ```python
132
+ print(l)
133
+ ```
134
+
135
+ ### Data Level
136
+
137
+ At this point, we have reached the end of the HoloViews objects; below this object is only the raw data stored in one of the supported formats (e.g. NumPy arrays or Pandas DataFrames):
138
+
139
+
140
+ ```python
141
+ type(layout.Parameters.Sines[0.5, 1][1].Phases.Sines[0.0].data)
142
+ ```
143
+
144
+ Actually, HoloViews will let you go even further down, accessing data inside the Numpy array using the continuous (floating-point) coordinate systems declared in HoloViews. E.g. here we can ask for a single datapoint, such as the value at x=5.2:
145
+
146
+
147
+ ```python
148
+ layout.Parameters.Sines[0.5, 1][1].Phases.Sines[0.0][5.2]
149
+ ```
150
+
151
+ Indexing into 1D Elements like Curve and higher-dimensional but regularly gridded Elements like Image, Surface, and HeatMap will return the nearest defined value (i.e., the results "snap" to the nearest data item):
152
+
153
+
154
+ ```python
155
+ layout.Parameters.Sines[0.5, 1][1].Phases.Sines[0.0][5.23], layout.Parameters.Sines[0.5, 1][1].Phases.Sines[0.0][5.27]
156
+ ```
157
+
158
+ For other Element types, such as Points, snapping is not supported and thus indexing down into the .data array will be less useful, because it will only succeed for a perfect floating-point match on the key dimensions. In those cases, you can still use all of the access methods provided by the numpy array itself, via ``.data``, e.g. ``.data[52]``, but note that such native operations force you to use the native indexing scheme of the array, i.e. integer access starting at zero, not the more convenient and semantically meaningful [continuous coordinate systems](./Continuous_Coordinates.ipynb) we provide through HoloViews.
159
+
160
+ ### Indexing using ``.select``
161
+
162
+ The curve displayed immediately above shows the final, deepest Element access possible in HoloViews for this object:
163
+
164
+ ```python
165
+ layout.Parameters.Sines[0.5, 1][1].Phases.Sines[0.0]
166
+ ```
167
+ This is the curve with an amplitude of *0.5*, raised to a power of *1.0* with frequency of *1.0* and *0* phase. These are all the numbers, in order, used in the access shown above.
168
+
169
+ The ``.select`` method is a more explicit way to use key access, with both of these equivalent to each other:
170
+
171
+
172
+ ```python
173
+ o1 = layout.Parameters.Sines.select(Amplitude=0.5, Power=1.0).select(Frequency=1.0)
174
+ o2 = layout.Parameters.Sines.select(Amplitude=0.5, Power=1.0, Frequency=1.0)
175
+ o1 + o2
176
+ ```
177
+
178
+ The second form demonstrates HoloViews' **deep indexing** feature, which allows indices to cross nested container boundaries. The above is as far as we can index before reaching a heterogeneous type (the ``Overlay``), where we need to use attribute access. Here is the more explicit method of indexing down to a curve, using ``.select`` to specify dimensions by name instead of bracket-based indexing by position:
179
+
180
+
181
+ ```python
182
+ layout.Parameters.Sines.select(Amplitude=0.5,Power=1.0, Frequency=1.0, Phase=0.0).Phases.Sines
183
+ ```
184
+
185
+ ## Summary
186
+
187
+ As you can see, HoloViews lets you compose objects of heterogeneous types, and objects covering many different numerical or other dimensions, laying them out spatially, temporally, or overlaid. The resulting data structures are complex, but they are composed of simple elements with well-defined interactions, making it feasible to express nearly any relationship that will characterize your data. In practice, you will probably not need this many levels, but given this complete example, you should be able to construct an appropriate hierarchy for whatever type of data that you want to represent or visualize.
188
+
189
+ As emphasized above, it is not recommended to combine these objects in other orderings. Of course, any ``Element`` can be substituted for any other, which doesn't change the structure. But you should not e.g. display an ``Overlay`` of ``Layout`` objects. The display system will generally attempt to figure out the correct arrangement and warn you to call the `.collate` method to reorganize the objects in the recommended format.
190
+
191
+ Another important thing to observe is that ``Layout`` and ``Overlay`` types may not be nested, e.g. using the ``+`` operator on two `Layout` objects will not create a nested `Layout` instead combining the contents of the two objects. The same applies to the ``*`` operator and combining of `Overlay` objects.
hvplot_docs/07-Live_Data.md ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Live Data
2
+
3
+ The [HoloMap](../reference/containers/bokeh/HoloMap.ipynb) is a core HoloViews data structure that allows easy exploration of parameter spaces. The essence of a HoloMap is that it contains a collection of [Elements](http://holoviews.org/reference/index.html) (e.g. ``Image``s and ``Curve``s) that you can easily select and visualize.
4
+
5
+ HoloMaps hold fully constructed Elements at specifically sampled points in a multidimensional space. Although HoloMaps are useful for exploring high-dimensional parameter spaces, they can very quickly consume huge amounts of memory to store all these Elements. For instance, a hundred samples along four orthogonal dimensions would need a HoloMap containing a hundred *million* Elements, each of which could be a substantial object that takes time to create and costs memory to store. Thus ``HoloMaps`` have some clear limitations:
6
+
7
+ * HoloMaps may require the generation of millions of Elements before the first element can be viewed.
8
+ * HoloMaps can easily exhaust all the memory available to Python.
9
+ * HoloMaps can even more easily exhaust all the memory in the browser when displayed.
10
+ * Static export of a notebook containing HoloMaps can result in impractically large HTML files.
11
+
12
+ The ``DynamicMap`` addresses these issues by computing and displaying elements dynamically, allowing exploration of much larger datasets:
13
+
14
+ * DynamicMaps generate elements on the fly, allowing the process of exploration to begin immediately.
15
+ * DynamicMaps do not require fixed sampling, allowing exploration of parameters with arbitrary resolution.
16
+ * DynamicMaps are lazy in the sense they only compute as much data as the user wishes to explore.
17
+
18
+ Of course, these advantages come with some limitations:
19
+
20
+ * DynamicMaps require a live notebook server and cannot be fully exported to static HTML.
21
+ * DynamicMaps store only a portion of the underlying data, in the form of an Element cache and their output is dependent on the particular version of the executed code.
22
+ * DynamicMaps (and particularly their element caches) are typically stateful (with values that depend on patterns of user interaction), which can make them more difficult to reason about.
23
+
24
+ In addition to the different computational requirements of ``DynamicMaps``, they can be used to build sophisticated, interactive visualisations that cannot be achieved using only ``HoloMaps``. This notebook demonstrates some basic examples and the [Responding to Events](./12-Responding_to_Events.ipynb) guide follows on by introducing the streams system. The [Custom Interactivity](./13-Custom_Interactivity.ipynb) shows how you can directly interact with your plots when using the Bokeh backend.
25
+
26
+ When DynamicMap was introduced in version 1.6, it supported multiple different 'modes' which have now been deprecated. This notebook demonstrates the simpler, more flexible and more powerful DynamicMap introduced in version 1.7. Users who have been using the previous version of DynamicMap should be unaffected as backwards compatibility has been preserved for the most common cases.
27
+
28
+ All this will make much more sense once we've tried out some ``DynamicMaps`` and showed how they work, so let's create one!
29
+
30
+
31
+ <center><div class="alert alert-info" role="alert">To use visualize and use a <b>DynamicMap</b> you need to be running a live Jupyter server.<br>When viewing this user guide as part of the documentation DynamicMaps will be sampled with a limited number of states.<br>
32
+ It's also best to run this notebook one cell at a time, not via "Run All",<br> so that subsequent cells can reflect your dynamic interaction with widgets in previous cells.</div></center>
33
+
34
+ ## ``DynamicMap`` <a id='DynamicMap'></a>
35
+
36
+ Let's start by importing HoloViews and loading the extension:
37
+
38
+
39
+ ```python
40
+ import numpy as np
41
+ import holoviews as hv
42
+ from holoviews import opts
43
+
44
+ hv.extension('matplotlib')
45
+ ```
46
+
47
+ We will now create ``DynamicMap`` similar to the ``HoloMap`` introduced in the [Introductory guide](../getting_started/1-Introduction.ipynb). The ``HoloMap`` in that introduction consisted of ``Image`` elements defined by a function returning NumPy arrays called ``sine_array``. Here we will define a ``waves_image`` function that returns an array pattern parameterized by arbitrary ``alpha`` and ``beta`` parameters inside a HoloViews
48
+ [``Image``](../reference/elements/bokeh/Image.ipynb) element:
49
+
50
+
51
+ ```python
52
+ xvals = np.linspace(-4, 0, 202)
53
+ yvals = np.linspace(4, 0, 202)
54
+ xs, ys = np.meshgrid(xvals, yvals)
55
+
56
+ def waves_image(alpha, beta):
57
+ return hv.Image(np.sin(((ys/alpha)**alpha+beta)*xs))
58
+
59
+ waves_image(1,0) + waves_image(1,4)
60
+ ```
61
+
62
+ Now we can demonstrate the possibilities for exploration enabled by the simplest declaration of a ``DynamicMap``.
63
+
64
+ ### Basic ``DynamicMap`` declaration<a id='BasicDeclaration'></a>
65
+
66
+ A simple ``DynamicMap`` declaration looks identical to that needed to declare a ``HoloMap``. Instead of supplying some initial data, we will supply the ``waves_image`` function with key dimensions simply declaring the arguments of that function:
67
+
68
+
69
+ ```python
70
+ dmap = hv.DynamicMap(waves_image, kdims=['alpha', 'beta'])
71
+ dmap
72
+ ```
73
+
74
+ This object is created instantly, but because it doesn't generate any `hv.Image` objects initially it only shows the printed representation of this object along with some information about how to display it. We will refer to a ``DynamicMap`` that doesn't have enough information to display itself as 'unbounded'.
75
+
76
+ The textual representation of all ``DynamicMaps`` look similar, differing only in the listed dimensions until they have been evaluated at least once.
77
+
78
+ #### Explicit indexing
79
+
80
+ Unlike a corresponding ``HoloMap`` declaration, this simple unbounded ``DynamicMap`` cannot yet visualize itself. To view it, we can follow the advice in the warning message. First we will explicitly index into our ``DynamicMap`` in the same way you would access a key on a ``HoloMap``:
81
+
82
+
83
+ ```python
84
+ dmap[1,2] + dmap.select(alpha=1, beta=2)
85
+ ```
86
+
87
+ Note that the declared kdims are specifying the arguments *by position* as they do not match the argument names of the ``waves_image`` function. If you *do* match the argument names *exactly*, you can map a kdim position to any argument position of the callable. For instance, the declaration ``kdims=['freq', 'phase']`` would index first by frequency, then phase without mixing up the arguments to ``waves_image`` when indexing.
88
+
89
+ #### Setting dimension ranges
90
+
91
+ The second suggestion in the warning message was to supply dimension ranges using the ``redim.range`` method:
92
+
93
+
94
+ ```python
95
+ dmap.redim.range(alpha=(1, 5.0), beta=(1, 6.0))
96
+ ```
97
+
98
+ Here each `hv.Image` object visualizing a particular sine ring pattern with the given parameters is created dynamically, whenever the slider is set to a new value. Any value in the allowable range can be requested by dragging the sliders or by tweaking the values using the left and right arrow keys.
99
+
100
+ Of course, we didn't have to use the ``redim.range`` method and we could have simply declared the ranges right away using explicit ``hv.Dimension`` objects. This would allow us to declare other dimension properties such as the step size used by the sliders: by default each slider can select around a thousand distinct values along its range but you can specify your own step value via the dimension ``step`` parameter. If you use integers in your range declarations, integer stepping will be assumed with a step size of one.
101
+
102
+ Note that whenever the ``redim`` method is used, a new ``DynamicMap`` is returned with the updated dimensions. In other words, the original ``dmap`` remains unbounded with default dimension objects.
103
+
104
+
105
+ #### Setting dimension values
106
+
107
+ The ``DynamicMap`` above allows exploration of *any* phase and frequency within the declared range unlike an equivalent ``HoloMap`` which would have to be composed of a finite set of samples. We can achieve a similar discrete sampling using ``DynamicMap`` by setting the ``values`` parameter on the dimensions:
108
+
109
+
110
+ ```python
111
+ dmap.redim.values(alpha=[1, 2, 3], beta=[0.1, 1.0, 2.5])
112
+ ```
113
+
114
+ The sliders now snap to the specified dimension values and if you are running this live, the above cell should look like a [HoloMap](../reference/containers/bokeh/HoloMap.ipynb). ``DynamicMap`` is in fact a subclass of ``HoloMap`` with some crucial differences:
115
+
116
+ * You can now pick as many values of **alpha** or **beta** as allowed by the slider.
117
+ * What you see in the cell above will not be exported in any HTML snapshot of the notebook
118
+
119
+ #### Getting the current key
120
+
121
+ The ``current_key`` property returns the current key of the ``DynamicMap``, i.e. either a value (one-dimensional) or a tuple (multi-dimensional) that represents the current state of the widgets. If you move the sliders above and re-execute the cell below you will see that the tuple returned reflects the values of *alpha* and *beta*.
122
+
123
+
124
+ ```python
125
+ dmap.current_key
126
+ ```
127
+
128
+ We will now explore how ``DynamicMaps`` relate to ``HoloMaps`` including conversion operations between the two types. As we will see, there are other ways to display a ``DynamicMap`` without using explicit indexing or redim.
129
+
130
+ ## Interaction with ``HoloMap``s
131
+
132
+ To explore the relationship between ``DynamicMap`` and ``HoloMap``, let's declare another callable to draw some shapes we will use in a new ``DynamicMap``:
133
+
134
+
135
+ ```python
136
+ def shapes(N, radius=0.5): # Positional keyword arguments are fine
137
+ paths = [hv.Path([[(radius*np.sin(a), radius*np.cos(a))
138
+ for a in np.linspace(-np.pi, np.pi, n+2)]],
139
+ extents=(-1,-1,1,1))
140
+ for n in range(N,N+3)]
141
+ return hv.Overlay(paths)
142
+ ```
143
+
144
+ #### Sampling ``DynamicMap`` from a ``HoloMap``
145
+
146
+ When combining a ``HoloMap`` with a ``DynamicMap``, it would be very awkward to have to match the declared dimension ``values`` of the DynamicMap with the keys of the ``HoloMap``. Fortunately you don't have to:
147
+
148
+
149
+ ```python
150
+ holomap = hv.HoloMap({(N,r):shapes(N, r) for N in [3,4,5] for r in [0.5,0.75]}, kdims=['N', 'radius'])
151
+ dmap = hv.DynamicMap(shapes, kdims=['N','radius'])
152
+ holomap + dmap
153
+ ```
154
+
155
+ Here we declared a ``DynamicMap`` without using ``redim``, but we can view its output because it is presented alongside a ``HoloMap`` which defines the available keys. This convenience is subject to three particular restrictions:
156
+
157
+
158
+ * You cannot display a layout consisting of unbounded ``DynamicMaps`` only, because at least one HoloMap is needed to define the samples.
159
+ * The HoloMaps provide the necessary information required to sample the DynamicMap.
160
+
161
+ Note that there is one way ``DynamicMap`` is less restricted than ``HoloMap``: you can freely combine bounded ``DynamicMaps`` together in a ``Layout``, even if they don't share key dimensions.
162
+
163
+ #### Converting from ``DynamicMap`` to ``HoloMap``
164
+
165
+ Above we mentioned that ``DynamicMap`` is an instance of ``HoloMap``. Does this mean it has a ``.data`` attribute?
166
+
167
+
168
+ ```python
169
+ dtype = type(dmap.data).__name__
170
+ length = len(dmap.data)
171
+ print("DynamicMap 'dmap' has an {dtype} .data attribute of length {length}".format(dtype=dtype, length=length))
172
+ ```
173
+
174
+ This is exactly the same sort of ``.data`` as the equivalent ``HoloMap``, except that its values will vary according to how much you explored the parameter space of ``dmap`` using the sliders above. In a ``HoloMap``, ``.data`` contains a defined sampling along the different dimensions, whereas in a ``DynamicMap``, the ``.data`` is simply the *cache*.
175
+
176
+ The cache serves two purposes:
177
+
178
+ * Avoids recomputation of an element should we revisit a particular point in the parameter space. This works well for categorical or integer dimensions, but doesn't help much when using continuous sliders for real-valued dimensions.
179
+ * Records the space that has been explored with the ``DynamicMap`` for any later conversion to a ``HoloMap`` up to the allowed cache size.
180
+
181
+ We can always convert *any* ``DynamicMap`` directly to a ``HoloMap`` as follows:
182
+
183
+
184
+ ```python
185
+ hv.HoloMap(dmap)
186
+ ```
187
+
188
+ This is in fact equivalent to declaring a HoloMap with the same parameters (dimensions, etc.) using ``dmap.data`` as input, but is more convenient. Note that the slider positions reflect those we sampled from the ``HoloMap`` in the previous section.
189
+
190
+ Although creating a HoloMap this way is easy, the result is poorly controlled, as the keys in the DynamicMap cache are usually defined by how you moved the sliders around. If you instead want to specify a specific set of samples, you can easily do so by using the same key-selection semantics as for a ``HoloMap`` to define exactly which elements are to be sampled and put into the cache:
191
+
192
+
193
+ ```python
194
+ hv.HoloMap(dmap[{(2,0.3), (2,0.6), (3,0.3), (3,0.6)}])
195
+ ```
196
+
197
+ Here we index the ``dmap`` with specified keys to return a *new* DynamicMap with those keys in its cache, which we then cast to a ``HoloMap``. This allows us to export specific contents of ``DynamicMap`` to static HTML which will display the data at the sampled slider positions.
198
+
199
+ The key selection above happens to define a Cartesian product, which is one of the most common ways to sample across dimensions. Because the list of such dimension values can quickly get very large when enumerated as above, we provide a way to specify a Cartesian product directly, which also works with ``HoloMaps``. Here is an equivalent way of defining the same set of four points in that two-dimensional space:
200
+
201
+
202
+ ```python
203
+ samples = hv.HoloMap(dmap[{2,3},{0.5,1.0}])
204
+ samples
205
+ ```
206
+
207
+
208
+ ```python
209
+ samples.data.keys()
210
+ ```
211
+
212
+ The default cache size of 500 Elements is relatively high so that interactive exploration will work smoothly, but you can reduce it using the ``cache_size`` parameter if you find you are running into issues with memory consumption. A bounded ``DynamicMap`` with ``cache_size=1`` requires the least memory, but will recompute a new Element every time the sliders are moved, making it less responsive.
213
+
214
+ #### Converting from ``HoloMap`` to ``DynamicMap``
215
+
216
+ We have now seen how to convert from a ``DynamicMap`` to a ``HoloMap`` for the purposes of static export, but why would you ever want to do the inverse?
217
+
218
+ Although having a ``HoloMap`` to start with means it will not save you memory, converting to a ``DynamicMap`` does mean that the rendering process can be deferred until a new slider value requests an update. You can achieve this conversion using the ``Dynamic`` utility as demonstrated here by applying it to the previously defined ``HoloMap`` called ``samples``:
219
+
220
+
221
+ ```python
222
+ from holoviews.util import Dynamic
223
+ dynamic = Dynamic(samples)
224
+ print('After apply Dynamic, the type is a {dtype}'.format(dtype=type(dynamic).__name__))
225
+ dynamic
226
+ ```
227
+
228
+ In this particular example, there is no real need to use ``Dynamic`` as each frame renders quickly enough. For visualizations that are slow to render, using ``Dynamic`` can result in more responsive visualizations.
229
+
230
+ The ``Dynamic`` utility is very versatile and is discussed in more detail in the [Transforming Elements](./11-Transforming_Elements.ipynb) guide.
231
+
232
+ ### Slicing ``DynamicMaps``
233
+
234
+ As we have seen we can either declare dimension ranges directly in the kdims or use the ``redim.range`` convenience method:
235
+
236
+
237
+ ```python
238
+ dmap = hv.DynamicMap(shapes, kdims=['N','radius']).redim.range(N=(2,20), radius=(0.5,1.0))
239
+ ```
240
+
241
+ The declared dimension ranges define the absolute limits allowed for exploration in this continuous, bounded DynamicMap . That said, you can use the soft_range parameter to view subregions within that range. Setting the soft_range parameter on dimensions can be done conveniently using slicing:
242
+
243
+
244
+ ```python
245
+ sliced = dmap[4:8, :]
246
+ sliced
247
+ ```
248
+
249
+
250
+ Notice that N is now restricted to the range 4:8. Open slices are used to release any ``soft_range`` values, which resets the limits back to those defined by the full range:
251
+
252
+
253
+ ```python
254
+ sliced[:, 0.8:1.0]
255
+ ```
256
+
257
+ The ``[:]`` slice leaves the soft_range values alone and can be used as a convenient way to clone a ``DynamicMap``. Note that mixing slices with any other object type is not supported. In other words, once you use a single slice, you can only use slices in that indexing operation.
258
+
259
+ ## Using groupby to discretize a DynamicMap
260
+
261
+ A DynamicMap also makes it easy to partially or completely discretize a function to evaluate in a complex plot. By grouping over specific dimensions that define a fixed sampling via the Dimension values parameter, the DynamicMap can be viewed as a ``GridSpace``, ``NdLayout``, or ``NdOverlay``. If a dimension specifies only a continuous range it can't be grouped over, but it may still be explored using the widgets. This means we can plot partial or completely discretized views of a parameter space easily.
262
+
263
+ #### Partially discretize
264
+
265
+ The implementation for all the groupby operations uses the ``.groupby`` method internally, but we also provide three higher-level convenience methods to group dimensions into an ``NdOverlay`` (``.overlay``), ``GridSpace`` (``.grid``), or ``NdLayout`` (``.layout``).
266
+
267
+ Here we will evaluate a simple sine function with three dimensions, the phase, frequency, and amplitude. We assign the frequency and amplitude discrete samples, while defining a continuous range for the phase:
268
+
269
+
270
+ ```python
271
+ xs = np.linspace(0, 2*np.pi,100)
272
+
273
+ def sin(ph, f, amp):
274
+ return hv.Curve((xs, np.sin(xs*f+ph)*amp))
275
+
276
+ kdims=[hv.Dimension('phase', range=(0, np.pi)),
277
+ hv.Dimension('frequency', values=[0.1, 1, 2, 5, 10]),
278
+ hv.Dimension('amplitude', values=[0.5, 5, 10])]
279
+
280
+ waves_dmap = hv.DynamicMap(sin, kdims=kdims)
281
+ ```
282
+
283
+ Next we define the amplitude dimension to be overlaid and the frequency dimension to be gridded:
284
+
285
+
286
+ ```python
287
+ wave_grid = waves_dmap.overlay('amplitude').grid('frequency')
288
+ wave_grid.opts(fig_size=200, show_legend=True)
289
+ ```
290
+
291
+ As you can see, instead of having three sliders (one per dimension), we've now laid out the frequency dimension as a discrete set of values in a grid, and the amplitude dimension as a discrete set of values in an overlay, leaving one slider for the remaining dimension (phase). This approach can help you visualize a large, multi-dimensional space efficiently, with full control over how each dimension is made visible.
292
+
293
+
294
+ #### Fully discretize
295
+
296
+ Given a continuous function defined over a space, we could sample it manually, but here we'll look at an example of evaluating it using the groupby method. Let's look at a spiral function with a frequency and first- and second-order phase terms. Then we define the dimension values for all the parameters and declare the DynamicMap:
297
+
298
+
299
+ ```python
300
+ opts.defaults(opts.Path(color=hv.Palette('Blues')))
301
+
302
+ def spiral_equation(f, ph, ph2):
303
+ r = np.arange(0, 1, 0.005)
304
+ xs, ys = (r * fn(f*np.pi*np.sin(r+ph)+ph2) for fn in (np.cos, np.sin))
305
+ return hv.Path((xs, ys))
306
+
307
+ spiral_dmap = hv.DynamicMap(spiral_equation, kdims=['f','ph','ph2'])
308
+ spiral_dmap = spiral_dmap.redim.values(
309
+ f=np.linspace(1, 10, 10),
310
+ ph=np.linspace(0, np.pi, 10),
311
+ ph2=np.linspace(0, np.pi, 4))
312
+ ```
313
+
314
+ Now we can make use of the ``.groupby`` method to group over the frequency and phase dimensions, which we will display as part of a GridSpace by setting the ``container_type``. This leaves the second phase variable, which we assign to an NdOverlay by setting the ``group_type``:
315
+
316
+
317
+ ```python
318
+ spiral_grid = spiral_dmap.groupby(['f', 'ph'], group_type=hv.NdOverlay, container_type=hv.GridSpace)
319
+ spiral_grid.opts(
320
+ opts.GridSpace(xaxis=None, yaxis=None),
321
+ opts.Path(bgcolor='white', xaxis=None, yaxis=None))
322
+ ```
323
+
324
+ This grid shows a range of frequencies `f` on the x axis, a range of the first phase variable `ph` on the `y` axis, and a range of different `ph2` phases as overlays within each location in the grid. As you can see, these techniques can help you visualize multidimensional parameter spaces compactly and conveniently.
325
+
326
+
327
+ ## DynamicMaps and normalization
328
+
329
+ By default, a ``HoloMap`` normalizes the display of elements using the minimum and maximum values found across the ``HoloMap``. This automatic behavior is not possible in a ``DynamicMap``, where arbitrary new elements are being generated on the fly. Consider the following examples where the arrays contained within the returned ``Image`` objects are scaled with time:
330
+
331
+
332
+ ```python
333
+ ls = np.linspace(0, 10, 200)
334
+ xx, yy = np.meshgrid(ls, ls)
335
+
336
+ def cells(time):
337
+ return hv.Image(time*np.sin(xx+time)*np.cos(yy+time), vdims='Intensity')
338
+
339
+ dmap = hv.DynamicMap(cells, kdims='time').redim.range(time=(1,20))
340
+ (dmap + dmap.redim.range(Intensity=(0,10))).opts(
341
+ opts.Image(axiswise=True))
342
+ ```
343
+
344
+ Here we use ``axiswise=True`` to see the behavior of the two cases independently. We see in **A** that when only the time dimension is given a range, no automatic normalization occurs (unlike a ``HoloMap``). In **B** we see that normalization is applied, but only when the value dimension ('Intensity') range has been specified.
345
+
346
+ In other words, ``DynamicMaps`` cannot support automatic normalization across their elements, but do support the same explicit normalization behavior as ``HoloMaps``. Values that are generated outside this range are simply clipped in accord with the usual semantics of explicit value dimension ranges.
347
+
348
+ Note that we always have the option of casting a ``DynamicMap`` to a ``HoloMap`` in order to automatically normalize across the cached values, without needing explicit value dimension ranges.
349
+
350
+ ## Using DynamicMaps in your code
351
+
352
+ As you can see, ``DynamicMaps`` let you use HoloViews with a very wide range of dynamic data formats and sources, making it simple to visualize ongoing processes or very large data spaces.
353
+
354
+ Given unlimited computational resources, the functionality covered in this guide would match that offered by ``HoloMap`` but with fewer normalization options. ``DynamicMap`` actually enables a vast range of new possibilities for dynamic, interactive visualizations as covered in the [Responding to Events](./Responding_to_Events.ipynb) guide. Following on from that, the [Custom Interactivity](./13-Custom_Interactivity.ipynb) guide shows how you can directly interact with your plots when using the Bokeh backend.
hvplot_docs/08-Tabular_Datasets.md ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Tabular Datasets
2
+
3
+ In this guide we will explore how to work with tabular data in HoloViews. Tabular data has a fixed list of column headings, with values stored in an arbitrarily long list of rows. Spreadsheets, relational databases, CSV files, and many other typical data sources fit naturally into this format. HoloViews defines an extensible system of interfaces to load, manipulate, and visualize this kind of data, as well as allowing conversion of any of the non-tabular data types into tabular data for analysis or data interchange.
4
+
5
+ By default HoloViews will use one of these data storage formats for tabular data:
6
+
7
+ * A pure Python dictionary containing 1D NumPy-arrays for each column.
8
+
9
+ ``{'x': np.array([0, 1, 2]), 'y': np.array([0, 1, 2])}``
10
+
11
+ * A purely NumPy array format for numeric data.
12
+
13
+ ``np.array([[0, 0], [1, 1], [2, 3]])``
14
+
15
+ * Pandas DataFrames
16
+
17
+ ``pd.DataFrame(np.array([[0, 0], [1, 1], [2, 3]]), columns=['x', 'y'])``
18
+
19
+ * Dask DataFrames
20
+
21
+ * cuDF Dataframes
22
+
23
+ A number of additional standard constructors are supported:
24
+
25
+ * A tuple of array (or array-like) objects
26
+
27
+ ``([0, 1, 2], [0, 1, 2])``
28
+
29
+ * A list of tuples:
30
+
31
+ ``[(0, 0), (1, 1), (2, 2)]``
32
+
33
+
34
+
35
+ ```python
36
+ import numpy as np
37
+ import pandas as pd
38
+ import holoviews as hv
39
+ from holoviews import opts
40
+
41
+ hv.extension('bokeh', 'matplotlib')
42
+
43
+ opts.defaults(opts.Scatter(size=10))
44
+ ```
45
+
46
+ # A simple Dataset
47
+
48
+ Usually when working with data we have one or more independent variables, taking the form of categories, labels, discrete sample coordinates, or bins. We refer to these independent variables as key dimensions (or ``kdims`` for short) in HoloViews. The observer or dependent variables, on the other hand, are referred to as value dimensions (``vdims``), and are ordinarily measured or calculated given the independent variables. The simplest useful form of a ``Dataset`` object is therefore a column 'x' and a column 'y' corresponding to the key dimensions and value dimensions respectively. An obvious visual representation of this data is a ``Table``:
49
+
50
+
51
+ ```python
52
+ xs = np.linspace(0, 10, 11)
53
+ ys = np.sin(xs)
54
+
55
+ table = hv.Table((xs, ys), 'x', 'y')
56
+ table
57
+ ```
58
+
59
+ However, this data has many more meaningful visual representations, and therefore the first important concept is that ``Dataset`` objects can be converted to other objects as long as their dimensionality allows it, meaning that you can easily create the different objects from the same data (and cast between the objects once created):
60
+
61
+
62
+ ```python
63
+ (hv.Scatter(table) + hv.Curve(table) + hv.Area(table) + hv.Bars(table)).cols(2)
64
+ ```
65
+
66
+ Each of these three plots uses the same data, but represents a different assumption about the semantic meaning of that data -- the ``Scatter`` plot is appropriate if that data consists of independent samples, the ``Curve`` plot is appropriate for samples chosen from an underlying smooth function, and the ``Bars`` plot is appropriate for independent categories of data. Since all these plots have the same dimensionality, they can easily be converted to each other, but there is normally only one of these representations that is semantically appropriate for the underlying data. For this particular data, the semantically appropriate choice is ``Curve``, since the *y* values are samples from the continuous function ``exp``.
67
+
68
+ As a guide to which Elements can be converted to each other, those of the same dimensionality here should be interchangeable, because of the underlying similarity of their columnar representation:
69
+
70
+ * 0D: BoxWhisker, Spikes, Distribution
71
+ * 1D: Area, Bars, BoxWhisker, Curve, ErrorBars, Scatter, Spread
72
+ * 2D: Bars, Bivariate, BoxWhisker, HeatMap, Points, VectorField
73
+ * 3D: Scatter3D, TriSurface, VectorField
74
+
75
+ This categorization is based only on the ``kdims``, which define the space in which the data has been sampled or defined. An Element can also have any number of value dimensions (``vdims``), which may be mapped onto various attributes of a plot such as the color, size, and orientation of the plotted items. For a reference of how to use these various Element types, see the [Elements Reference](http://holoviews.org/reference/index.html#elements).
76
+
77
+ ## Data types and Constructors
78
+
79
+ As discussed above, ``Dataset`` provides an extensible interface to store and operate on data in different formats. All interfaces support a number of standard constructors.
80
+
81
+ #### Storage formats
82
+
83
+ Dataset types can be constructed using one of three supported formats, (a) a dictionary of columns, (b) an NxD array with N rows and D columns, or (c) pandas dataframes:
84
+
85
+
86
+ ```python
87
+ print(hv.Scatter({'x': xs, 'y': ys}) +
88
+ hv.Scatter(np.column_stack([xs, ys])) +
89
+ hv.Scatter(pd.DataFrame({'x': xs, 'y': ys})))
90
+ ```
91
+
92
+ #### Literals
93
+
94
+ In addition to the main storage formats, Dataset Elements support construction from three Python literal formats: (a) An iterator of y-values, (b) a tuple of columns, and (c) an iterator of row tuples.
95
+
96
+
97
+ ```python
98
+ print(hv.Scatter(ys) + hv.Scatter((xs, ys)) + hv.Scatter(zip(xs, ys)))
99
+ ```
100
+
101
+ For these inputs, the data will need to be copied to a new data structure, having one of the three storage formats above. By default Dataset will try to construct a simple array, falling back to either pandas dataframes (if available) or the dictionary-based format if the data is not purely numeric. Additionally, the interfaces will try to maintain the provided data's type, so numpy arrays and pandas DataFrames will always be parsed first by their respective array and dataframe interfaces.
102
+
103
+
104
+ ```python
105
+ df = pd.DataFrame({'x': xs, 'y': ys, 'z': ys*2})
106
+ print(type(hv.Scatter(df).data))
107
+ ```
108
+
109
+ Dataset will attempt to parse the supplied data, falling back to each consecutive interface if the previous could not interpret the data. The default list of fallbacks and simultaneously the list of allowed datatypes is:
110
+
111
+
112
+ ```python
113
+ hv.Dataset.datatype
114
+ ```
115
+
116
+ Note these include grid based datatypes, which are covered in [Gridded Datasets](http://holoviews.org/user_guide/Gridded_Datasets.html). To select a particular storage format explicitly, supply one or more allowed datatypes (note that the 'array' interface only supports data with matching types):
117
+
118
+
119
+ ```python
120
+ print(type(hv.Scatter((xs.astype('float64'), ys), datatype=['array']).data))
121
+ print(type(hv.Scatter((xs, ys), datatype=['dictionary']).data))
122
+ print(type(hv.Scatter((xs, ys), datatype=['dataframe']).data))
123
+ ```
124
+
125
+ #### Sharing Data
126
+
127
+ Since the formats with labelled columns do not require any specific order, each Element can effectively become a view into a single set of data. By specifying different key and value dimensions, many Elements can show different values, while sharing the same underlying data source.
128
+
129
+
130
+ ```python
131
+ overlay = hv.Scatter(df, 'x', 'y') * hv.Scatter(df, 'x', 'z')
132
+ overlay
133
+ ```
134
+
135
+ We can quickly confirm that the data is actually shared:
136
+
137
+
138
+ ```python
139
+ overlay.Scatter.I.data is overlay.Scatter.II.data
140
+ ```
141
+
142
+ For columnar data, this approach is much more efficient than creating copies of the data for each Element, and allows for some advanced features like linked brushing in the [Bokeh backend](./Plotting_with_Bokeh.ipynb).
143
+
144
+ #### Converting to raw data
145
+
146
+ Column types make it easy to export the data to the three basic formats: arrays, dataframes, and a dictionary of columns.
147
+
148
+ ###### Array
149
+
150
+
151
+ ```python
152
+ table.array()
153
+ ```
154
+
155
+ ###### Pandas DataFrame
156
+
157
+
158
+ ```python
159
+ table.dframe().head()
160
+ ```
161
+
162
+ ###### Dataset dictionary
163
+
164
+
165
+ ```python
166
+ table.columns()
167
+ ```
168
+
169
+ # Creating tabular data from Elements using the .table and .dframe methods
170
+
171
+ If you have data in some other HoloViews element and would like to use the columnar data features, you can easily tabularize any of the core Element types into a ``Table`` Element. Similarly, the ``.dframe()`` method will convert an Element into a pandas DataFrame. These methods are very useful if you want to then transform the data into a different Element type, or to perform different types of analysis.
172
+
173
+ ## Tabularizing simple Elements
174
+
175
+ For a simple example, we can create a ``Curve`` of an exponential function and cast it to a ``Table``, with the same result as creating the Table directly from the data as done earlier in this user guide:
176
+
177
+
178
+ ```python
179
+ xs = np.arange(10)
180
+ curve = hv.Curve(zip(xs, np.sin(xs)))
181
+ curve * hv.Scatter(curve) + hv.Table(curve)
182
+ ```
183
+
184
+ Similarly, we can get a pandas dataframe of the Curve using ``curve.dframe()``:
185
+
186
+
187
+ ```python
188
+ curve.dframe()
189
+ ```
190
+
191
+ ## Collapsing dimensioned containers
192
+
193
+ Even deeply nested objects can be deconstructed in this way, serializing them to make it easier to get your raw data out of a collection of specialized ``Element`` types. Let's say we want to make multiple observations of a noisy signal. We can collect the data into a ``HoloMap`` to visualize it and then call ``.collapse()`` to get a ``Dataset`` object to which we can apply operations or transformations to other ``Element`` types. Deconstructing nested data in this way only works if the data is homogeneous. In practical terms this requires that your data structure contains Elements (of any type) held in these Container types: ``NdLayout``, ``GridSpace``, ``HoloMap``, and ``NdOverlay``, with all dimensions consistent throughout (so that they can all fit into the same set of columns). To read more about these containers see the [Dimensioned Containers](./Dimensioned_Containers.ipynb) guide.
194
+
195
+ Let's now go back to the ``Image`` example. We will collect a number of observations of some noisy data into a ``HoloMap`` and display it:
196
+
197
+
198
+ ```python
199
+ obs_hmap = hv.HoloMap({i: hv.Image(np.random.randn(10, 10), bounds=(0,0,3,3))
200
+ for i in range(3)}, kdims='Observation')
201
+ obs_hmap
202
+ ```
203
+
204
+ Now we can serialize this data just as before, where this time we get a four-column (4D) table. The key dimensions of both the HoloMap and the Images, as well as the z-values of each ``Image``, are all merged into a single table. We can visualize the samples we have collected by converting it to a ``Scatter3D`` object.
205
+
206
+
207
+ ```python
208
+ hv.output(backend='matplotlib', size=150)
209
+
210
+ collapsed = obs_hmap.collapse()
211
+ scatter_layout = collapsed.to.scatter3d() + hv.Table(collapsed)
212
+ scatter_layout.opts(
213
+ opts.Scatter3D(color='z', cmap='hot', edgecolor='black', s=50))
214
+ ```
215
+
216
+ Here the `z` dimension is shown by color, as in the original images, and the other three dimensions determine where the datapoint is shown in 3D. This way of deconstructing objects will work for any data structure that satisfies the conditions described above, no matter how nested. If we vary the amount of noise while continuing to performing multiple observations, we can create an ``NdLayout`` of HoloMaps, one for each noise level, and animated by the observation number.
217
+
218
+
219
+ ```python
220
+ extents = (0, 0, 3, 3)
221
+
222
+ error_hmap = hv.HoloMap({
223
+ (i, j): hv.Image(j*np.random.randn(3, 3), bounds=extents)
224
+ for i in range(3) for j in np.linspace(0, 1, 3)},
225
+ ['Observation', 'noise'])
226
+
227
+ noise_layout = error_hmap.layout('noise')
228
+ noise_layout
229
+ ```
230
+
231
+ And again, we can easily convert the object to a ``Table``:
232
+
233
+
234
+ ```python
235
+ hv.Table(noise_layout.collapse())
236
+ ```
237
+
238
+ # Applying operations to the data
239
+
240
+ #### Sorting by columns
241
+
242
+ Once data is in columnar form, it is simple to apply a variety of operations. For instance, Dataset can be sorted by their dimensions using the ``.sort()`` method. By default, this method will sort by the key dimensions in an ascending order, but any other dimension(s) can be sorted by providing them as an argument list to the sort method. The ``reverse`` argument also allows sorting in descending order:
243
+
244
+
245
+ ```python
246
+ hv.output(backend='bokeh')
247
+
248
+ bars = hv.Bars((['C', 'A', 'B', 'D'], [2, 7, 3, 4]))
249
+ (bars +
250
+ bars.sort().relabel('sorted') +
251
+ bars.sort(['y']).relabel('y-sorted') +
252
+ bars.sort(reverse=True).relabel('reverse sorted')
253
+ ).opts(shared_axes=False).cols(2)
254
+ ```
255
+
256
+ #### Working with categorical or grouped data
257
+
258
+ Data is often grouped in various ways, and the Dataset interface provides various means to easily compare between groups and apply statistical aggregates. We'll start by generating some synthetic data with two groups along the x axis and 4 groups along the y axis.
259
+
260
+
261
+ ```python
262
+ n = np.arange(1000)
263
+ xs = np.repeat(range(2), 500)
264
+ ys = n%4
265
+ zs = np.random.randn(1000)
266
+ table = hv.Table((xs, ys, zs), ['x', 'y'], 'z')
267
+ table
268
+ ```
269
+
270
+ Since there are repeat observations of the same x- and y-values, we may want to reduce the data before we display it or else use a datatype that supports plotting distributions in this way. The ``BoxWhisker`` type allows doing exactly that:
271
+
272
+
273
+ ```python
274
+ hv.BoxWhisker(table)
275
+ ```
276
+
277
+ ### Aggregating/Reducing dimensions
278
+
279
+ Most types require the data to be non-duplicated before being displayed. For this purpose, HoloViews makes it easy to ``aggregate`` and ``reduce`` the data. These two operations are simple complements of each other--aggregate computes a statistic for each group in the supplied dimensions, while reduce combines all the groups except the supplied dimensions. Supplying only a function and no dimensions will simply aggregate or reduce all available key dimensions.
280
+
281
+
282
+ ```python
283
+ hv.Bars(table).aggregate('x', function=np.mean) + hv.Bars(table).reduce(x=np.mean)
284
+ ```
285
+
286
+ (**A**) aggregates over both the x and y dimension, computing the mean for each x/y group, while (**B**) reduces the x dimension leaving just the mean for each group along y.
287
+
288
+ ##### Collapsing multiple Dataset Elements
289
+
290
+ When multiple observations are broken out into a ``HoloMap`` they can easily be combined using the ``collapse`` method. Here we create a number of Curves with increasingly larger y-values. By collapsing them with a ``function`` and a ``spreadfn`` we can compute the mean curve with a confidence interval. We then simply cast the collapsed ``Curve`` to a ``Spread`` and ``Curve`` Element to visualize them.
291
+
292
+
293
+ ```python
294
+ hmap = hv.HoloMap({i: hv.Curve(np.arange(10)*i) for i in range(10)})
295
+ collapsed = hmap.collapse(function=np.mean, spreadfn=np.std)
296
+ hv.Spread(collapsed) * hv.Curve(collapsed) + hv.Table(collapsed)
297
+ ```
hvplot_docs/09-Gridded_Datasets.md ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gridded Datasets
2
+
3
+
4
+ ```python
5
+ import xarray as xr
6
+ import numpy as np
7
+ import holoviews as hv
8
+ from holoviews import opts
9
+ hv.extension('matplotlib')
10
+
11
+ opts.defaults(opts.Scatter3D(color='Value', cmap='fire', edgecolor='black', s=50))
12
+ ```
13
+
14
+ In the [Tabular Data](./08-Tabular_Datasets.ipynb) guide we covered how to work with columnar data in HoloViews. Apart from tabular or column based data there is another data format that is particularly common in the science and engineering contexts, namely multi-dimensional arrays. The gridded data interfaces allow working with grid-based datasets directly.
15
+
16
+ Grid-based datasets have two types of dimensions:
17
+
18
+ * they have coordinate or key dimensions, which describe the sampling of each dimension in the value arrays
19
+ * they have value dimensions which describe the quantity of the multi-dimensional value arrays
20
+
21
+ There are many different types of gridded datasets, which each approximate or measure a surface or space at discretely specified coordinates. In HoloViews, gridded datasets are typically one of three possible types: Regular rectilinear grids, irregular rectilinear grids, and curvilinear grids. Regular rectilinear grids can be defined by 1D coordinate arrays specifying the spacing along each dimension, while the other types require grid coordinates with the same dimensionality as the underlying value arrays, specifying the full n-dimensional coordinates of the corresponding array value. HoloViews provides many different elements supporting regularly spaced rectilinear grids, but currently only QuadMesh supports irregularly spaced rectilinear and curvilinear grids.
22
+
23
+ The difference between uniform, rectilinear and curvilinear grids is best illustrated by the figure below:
24
+
25
+ <figure>
26
+ <img src="http://earthsystemmodeling.org/docs/release/ESMF_8_1_1/ESMC_crefdoc/img11.png" alt="grid-types">
27
+ <figcaption>Types of logically rectangular grid tiles. Red circles show the values needed to specify grid coordinates for each type. Reproduced from <a href="http://earthsystemmodeling.org/docs/release/ESMF_8_1_1/ESMC_crefdoc/node5.html">ESMF documentation</a></figcaption>
28
+ </figure>
29
+
30
+
31
+ In this section we will first discuss how to work with the simpler rectilinear grids and then describe how to define a curvilinear grid with 2D coordinate arrays.
32
+
33
+ ## Declaring gridded data
34
+
35
+ All Elements that support a ColumnInterface also support the GridInterface. The simplest example of a multi-dimensional (or more precisely 2D) gridded dataset is an image, which has implicit or explicit x-coordinates, y-coordinates and an array representing the values for each combination of these coordinates. Let us start by declaring an Image with explicit x- and y-coordinates:
36
+
37
+
38
+ ```python
39
+ img = hv.Image((range(10), range(5), np.random.rand(5, 10)), datatype=['grid'])
40
+ img
41
+ ```
42
+
43
+ In the above example we defined that there would be 10 samples along the x-axis, 5 samples along the y-axis and then defined a random ``5x10`` array, matching those dimensions. This follows the NumPy (row, column) indexing convention. When passing a tuple HoloViews will use the first gridded data interface, which stores the coordinates and value arrays as a dictionary mapping the dimension name to a NumPy array representing the data:
44
+
45
+
46
+ ```python
47
+ img.data
48
+ ```
49
+
50
+ However HoloViews also ships with an interface for ``xarray`` and the [GeoViews](https://geoviews.org) library ships with an interface for ``iris`` objects, which are two common libraries for working with multi-dimensional datasets:
51
+
52
+
53
+ ```python
54
+ arr_img = img.clone(datatype=['image'])
55
+ print(type(arr_img.data))
56
+
57
+ try:
58
+ xr_img = img.clone(datatype=['xarray'])
59
+
60
+ print(type(xr_img.data))
61
+ except:
62
+ print('xarray interface could not be imported.')
63
+ ```
64
+
65
+ In the case of an Image HoloViews also has a simple image representation which stores the data as a single array and converts the x- and y-coordinates to a set of bounds:
66
+
67
+
68
+ ```python
69
+ print("Array type: %s with bounds %s" % (type(arr_img.data), arr_img.bounds))
70
+ ```
71
+
72
+ To summarize the constructor accepts a number of formats where the value arrays should always match the shape of the coordinate arrays:
73
+
74
+ 1. A simple np.ndarray along with (l, b, r, t) bounds
75
+ 2. A tuple of the coordinate and value arrays
76
+ 3. A dictionary of the coordinate and value arrays indexed by their dimension names
77
+ 3. XArray DataArray or XArray Dataset
78
+ 4. An Iris cube
79
+
80
+ # Working with a multi-dimensional dataset
81
+
82
+ A gridded Dataset may have as many dimensions as desired, however individual Element types only support data of a certain dimensionality. Therefore we usually declare a ``Dataset`` to hold our multi-dimensional data and take it from there.
83
+
84
+
85
+ ```python
86
+ dataset3d = hv.Dataset((range(3), range(5), range(7), np.random.randn(7, 5, 3)),
87
+ ['x', 'y', 'z'], 'Value')
88
+ dataset3d
89
+ ```
90
+
91
+ This is because even a 3D multi-dimensional array represents volumetric data which we can display easily only if it contains few samples. In this simple case we can get an overview of what this data looks like by casting it to a ``Scatter3D`` Element (which will help us visualize the operations we are applying to the data:
92
+
93
+
94
+ ```python
95
+ hv.Scatter3D(dataset3d)
96
+ ```
97
+
98
+ ### Indexing
99
+
100
+ In order to explore the dataset we therefore often want to define a lower dimensional slice into the array and then convert the dataset:
101
+
102
+
103
+ ```python
104
+ dataset3d.select(x=1).to(hv.Image, ['y', 'z']) + hv.Scatter3D(dataset3d.select(x=1))
105
+ ```
106
+
107
+ ### Groupby
108
+
109
+ Another common method to apply to our data is to facet or animate the data using ``groupby`` operations. HoloViews provides a convenient interface to apply ``groupby`` operations and select which dimensions to visualize.
110
+
111
+
112
+ ```python
113
+ (dataset3d.to(hv.Image, ['y', 'z'], 'Value', ['x']) +
114
+ hv.HoloMap({x: hv.Scatter3D(dataset3d.select(x=x)) for x in range(3)}, kdims='x'))
115
+ ```
116
+
117
+ ### Aggregating
118
+
119
+ Another common operation is to aggregate the data with a function thereby reducing a dimension. You can either ``aggregate`` the data by passing the dimensions to aggregate or ``reduce`` a specific dimension. Both have the same function:
120
+
121
+
122
+ ```python
123
+ hv.Image(dataset3d.aggregate(['x', 'y'], np.mean)) + hv.Image(dataset3d.reduce(z=np.mean))
124
+ ```
125
+
126
+ By aggregating the data we can reduce it to any number of dimensions we want. We can for example compute the spread of values for each z-coordinate and plot it using a ``Spread`` and ``Curve`` Element. We simply aggregate by that dimension and pass the aggregation functions we want to apply:
127
+
128
+
129
+ ```python
130
+ hv.Spread(dataset3d.aggregate('z', np.mean, np.std)) * hv.Curve(dataset3d.aggregate('z', np.mean))
131
+ ```
132
+
133
+ It is also possible to generate lower-dimensional views into the dataset which can be useful to summarize the statistics of the data along a particular dimension. A simple example is a box-whisker of the ``Value`` for each x-coordinate. Using the ``.to`` conversion interface we declare that we want a ``BoxWhisker`` Element indexed by the ``x`` dimension showing the ``Value`` dimension. Additionally we have to ensure to set ``groupby`` to an empty list because by default the interface will group over any remaining dimension.
134
+
135
+
136
+ ```python
137
+ dataset3d.to(hv.BoxWhisker, 'x', 'Value', groupby=[])
138
+ ```
139
+
140
+ Similarly we can generate a ``Distribution`` Element showing the ``Value`` dimension, group by the 'x' dimension and then overlay the distributions, giving us another statistical summary of the data:
141
+
142
+
143
+ ```python
144
+ dataset3d.to(hv.Distribution, 'Value', [], groupby='x').overlay()
145
+ ```
146
+
147
+ ## Categorical dimensions
148
+
149
+ The key dimensions of the multi-dimensional arrays do not have to represent continuous values, we can display datasets with categorical variables as a ``HeatMap`` Element:
150
+
151
+
152
+ ```python
153
+ heatmap = hv.HeatMap((['A', 'B', 'C'], ['a', 'b', 'c', 'd', 'e'], np.random.rand(5, 3)))
154
+ heatmap + hv.Table(heatmap)
155
+ ```
156
+
157
+ ## Non-uniform rectilinear grids
158
+
159
+ As discussed above, there are two main types of grids handled by HoloViews. So far, we have mainly dealt with uniform, rectilinear grids, but we can use the ``QuadMesh`` element to work with non-uniform rectilinear grids and curvilinear grids.
160
+
161
+ In order to define a non-uniform, rectilinear grid we can declare explicit irregularly spaced x- and y-coordinates. In the example below we specify the x/y-coordinate bin edges of the grid as arrays of shape ``M+1`` and ``N+1`` and a value array (``zs``) of shape ``NxM``:
162
+
163
+
164
+ ```python
165
+ n = 8 # Number of bins in each direction
166
+ xs = np.logspace(1, 3, n)
167
+ ys = np.linspace(1, 10, n)
168
+ zs = np.arange((n-1)**2).reshape(n-1, n-1)
169
+ print('Shape of x-coordinates:', xs.shape)
170
+ print('Shape of y-coordinates:', ys.shape)
171
+ print('Shape of value array:', zs.shape)
172
+ hv.QuadMesh((xs, ys, zs))
173
+ ```
174
+
175
+ ## Curvilinear grids
176
+
177
+ To define a curvilinear grid the x/y-coordinates of the grid should be defined as 2D arrays of shape ``NxM`` or ``N+1xM+1``, i.e. either as the bin centers or the bin edges of each 2D bin.
178
+
179
+
180
+ ```python
181
+ n=20
182
+ coords = np.linspace(-1.5,1.5,n)
183
+ X,Y = np.meshgrid(coords, coords);
184
+ Qx = np.cos(Y) - np.cos(X)
185
+ Qy = np.sin(Y) + np.sin(X)
186
+ Z = np.sqrt(X**2 + Y**2)
187
+
188
+ print('Shape of x-coordinates:', Qx.shape)
189
+ print('Shape of y-coordinates:', Qy.shape)
190
+ print('Shape of value array:', Z.shape)
191
+
192
+ qmesh = hv.QuadMesh((Qx, Qy, Z))
193
+ qmesh
194
+ ```
195
+
196
+ ## Working with xarray data types
197
+ As demonstrated previously, `Dataset` comes with support for the `xarray` library, which offers a powerful way to work with multi-dimensional, regularly spaced data. In this example, we'll load an example dataset, turn it into a HoloViews `Dataset` and visualize it. First, let's have a look at the xarray dataset's contents:
198
+
199
+
200
+ ```python
201
+ xr_ds = xr.tutorial.open_dataset("air_temperature").load()
202
+ xr_ds
203
+ ```
204
+
205
+ It is trivial to turn this xarray Dataset into a Holoviews `Dataset` (the same also works for DataArray):
206
+
207
+
208
+ ```python
209
+ hv_ds = hv.Dataset(xr_ds)[:, :, "2013-01-01"]
210
+ print(hv_ds)
211
+ ```
212
+
213
+ We have used the usual slice notation in order to select one single day in the rather large dataset. Finally, let's visualize the dataset by converting it to a `HoloMap` of `Images` using the `to()` method. We need to specify which of the dataset's key dimensions will be consumed by the images (in this case "lat" and "lon"), where the remaining key dimensions will be associated with the HoloMap (here: "time"). We'll use the slice notation again to clip the longitude.
214
+
215
+
216
+ ```python
217
+ airtemp = hv_ds.to(hv.Image, kdims=["lon", "lat"], dynamic=False)
218
+ airtemp[:, 220:320, :].opts(colorbar=True, fig_size=200)
219
+ ```
220
+
221
+ Here, we have explicitly specified the default behaviour `dynamic=False`, which returns a HoloMap. Note, that this approach immediately converts all available data to images, which will take up a lot of RAM for large datasets. For these situations, use `dynamic=True` to generate a [DynamicMap](./07-Live_Data.ipynb) instead. Additionally, [xarray features dask support](http://xarray.pydata.org/en/stable/dask.html), which is helpful when dealing with large amounts of data.
222
+
223
+ It is also possible to render curvilinear grids with xarray, and here we will load one such example. The dataset below defines a curvilinear grid of air temperatures varying over time. The curvilinear grid can be identified by the fact that the ``xc`` and ``yc`` coordinates are defined as two-dimensional arrays:
224
+
225
+
226
+ ```python
227
+ rasm = xr.tutorial.open_dataset("rasm").load()
228
+ rasm.coords
229
+ ```
230
+
231
+ To simplify the example we will select a single timepoint and add explicit coordinates for the x and y dimensions:
232
+
233
+
234
+ ```python
235
+ rasm = rasm.isel(time=0, x=slice(0, 200)).assign_coords(x=np.arange(200), y=np.arange(205))
236
+ rasm.coords
237
+ ```
238
+
239
+ Now that we have defined both rectilinear and curvilinear coordinates we can visualize the difference between the two by explicitly defining which set of coordinates to use:
240
+
241
+
242
+ ```python
243
+ hv.QuadMesh(rasm, ['x', 'y']) + hv.QuadMesh(rasm, ['xc', 'yc'])
244
+ ```
245
+
246
+
247
+
248
+ Additional examples of visualizing xarrays in the context of geographical data can be found in the GeoViews documentation: [Gridded Datasets I](http://geoviews.org/user_guide/Gridded_Datasets_I.html) and
249
+ [Gridded Datasets II](http://geoviews.org/user_guide/Gridded_Datasets_II.html). These guides also contain useful information on the interaction between xarray data structures and HoloViews Datasets in general.
250
+
251
+ # API
252
+
253
+ ## Accessing the data
254
+
255
+ In order to be able to work with data in different formats Holoviews defines a general interface to access the data. The dimension_values method allows returning underlying arrays.
256
+
257
+ #### Key dimensions (coordinates)
258
+
259
+ By default ``dimension_values`` will return the expanded columnar format of the data:
260
+
261
+
262
+ ```python
263
+ heatmap.dimension_values('x')
264
+ ```
265
+
266
+ To access just the unique coordinates along a dimension simply supply the ``expanded=False`` keyword:
267
+
268
+
269
+ ```python
270
+ heatmap.dimension_values('x', expanded=False)
271
+ ```
272
+
273
+ Finally we can also get a non-flattened, expanded coordinate array returning a coordinate array of the same shape as the value arrays
274
+
275
+
276
+ ```python
277
+ heatmap.dimension_values('x', flat=False)
278
+ ```
279
+
280
+ #### Value dimensions
281
+
282
+ When accessing a value dimension the method will similarly return a flat view of the data:
283
+
284
+
285
+ ```python
286
+ heatmap.dimension_values('z')
287
+ ```
288
+
289
+ We can pass the ``flat=False`` argument to access the multi-dimensional array:
290
+
291
+
292
+ ```python
293
+ heatmap.dimension_values('z', flat=False)
294
+ ```
hvplot_docs/10-Indexing_and_Selecting_Data.md ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Indexing and Selecting data
2
+
3
+ As explained in the [Building composite objects](./06-Building_Composite_Objects.ipynb) and [Dimensioned Containers](./05-Dimensioned_Containers.ipynb) guides, HoloViews allows building up hierarchical containers that express the natural relationships between data items, in whatever multidimensional space best characterizes the application domain. Once your data is in such containers, individual visualizations are then made by choosing subregions of this multidimensional space, either smaller numeric ranges (as in cropping of photographic images), or lower-dimensional subsets (as in selecting frames from a movie, or a specific movie from a large library), or both (as in selecting a cropped version of a frame from a specific movie from a large library).
4
+
5
+ In this user guide, we show how to specify such selections, using five different (but related) operations that can act on an element ``e``:
6
+
7
+ | Operation | Example syntax | Description |
8
+ |:---------------|:----------------:|:-------------|
9
+ | **indexing** | e[5.5], e[3,5.5] | Selecting a single data value, returning one actual numerical value from the existing data
10
+ | **slice** | e[3:5.5], e[3:5.5,0:1] | Selecting a contiguous portion from an Element, returning the same type of Element
11
+ | **sample** | e.sample(y=5.5),<br>e.sample((3,3)) | Selecting one or more regularly spaced data values, returning a new type of Element
12
+ | **select** | e.select(y=5.5),<br>e.select(y=(3,5.5)) | More verbose notation covering all supported slice and index operations by dimension name.
13
+ | **iloc** | e[2, :],<br>e[2:5, :] | Indexes and slices by row and column tabular index supporting integer indexes, slices, lists and boolean indices.
14
+
15
+ These operations are all concerned with selecting some subset of the data values, without combining across data values (e.g. averaging) or otherwise transforming the actual data. In the [Tabular Data](./08-Tabular_Datasets.ipynb) user guide we will look at additional operations on the data that reduce, summarize, or transform the data in other ways, in addition to the selections covered here.
16
+
17
+ We'll be going through each operation in detail and provide a visual illustration to help make the semantics of each operation clear. This user guide assumes that you are familiar with continuous and discrete coordinate systems, so please review our [Continuous Coordinates](Continuous_Coordinates.ipynb) guide if you have not done so already.
18
+
19
+
20
+ ```python
21
+ import numpy as np
22
+ import holoviews as hv
23
+ from holoviews import opts
24
+
25
+ hv.extension('bokeh', 'matplotlib')
26
+
27
+ opts.defaults(
28
+ opts.Bounds(line_width=2, color='red', axiswise=True),
29
+ opts.Image(cmap='Blues'),
30
+ opts.Points(size=8, padding=0.1),
31
+ opts.Text(text_font_size='16pt'), opts.Scatter(size=5))
32
+ ```
33
+
34
+ # Indexing and slicing Elements
35
+
36
+ In the [Dimensioned Containers](./05-Dimensioned_Containers.ipynb) guide we saw examples of how to select individual elements embedded in a multi-dimensional space. The [Continuous Coordinates](Continuous_Coordinates.ipynb) user guide covered slicing and indexing in Elements representing continuous coordinate coordinate systems such as ``Image`` types. Here we'll be going through each operation in full detail, providing a visual illustration to help make the semantics of each operation clear.
37
+
38
+ How the ``Element`` may be indexed depends on the key dimensions (or ``kdims``) of the ``Element``. It is thus important to consider the nature and dimensionality of your data when choosing the ``Element`` type for it.
39
+
40
+ ## 1D Elements: Slicing and indexing
41
+
42
+ Certain Chart elements support both single-dimensional indexing and slicing: ``Scatter``, ``Curve``, ``Histogram``, and ``ErrorBars``. Here we'll look at how we can easily slice a ``Histogram`` to select a subregion of it:
43
+
44
+
45
+ ```python
46
+ np.random.seed(42)
47
+ edges, data = np.histogram(np.random.randn(100))
48
+ hist = hv.Histogram((edges, data))
49
+ subregion = hist[0:1]
50
+ hist * subregion
51
+ ```
52
+
53
+ The two bins in a different color show the selected region, overlaid on top of the full histogram. We can also access the value for a specific bin in the ``Histogram``. A continuous-valued index that falls inside a particular bin will return the corresponding value or frequency.
54
+
55
+
56
+ ```python
57
+ hist[0.25], hist[0.5], hist[0.55]
58
+ ```
59
+
60
+ We can slice a ``Curve`` the same way:
61
+
62
+
63
+ ```python
64
+ xs = np.linspace(0, np.pi*2, 21)
65
+ curve = hv.Curve((xs, np.sin(xs)))
66
+ subregion = curve[np.pi/2:np.pi*1.5]
67
+ curve * subregion * hv.Scatter(curve)
68
+ ```
69
+
70
+ Here again the region in a different color is the specified subregion. We've also marked each discrete point with a dot using the ``Scatter`` ``Element``. As before we can also get the value for a specific sample point; whatever x-index is provided will snap to the closest sample point and return the dependent value:
71
+
72
+
73
+ ```python
74
+ curve[4.05], curve[4.1], curve[4.17], curve[4.3]
75
+ ```
76
+
77
+ It is important to note that an index (or a list of indices, as for the 2D and 3D cases below) will always return the raw indexed (dependent) value, i.e. a number. A slice (indicated with `:`), on the other hand, will retain the Element type even in cases where the plot might not be useful, such as having only a single value, two values, or no value at all in that range:
78
+
79
+
80
+ ```python
81
+ curve[4:4.5]
82
+ ```
83
+
84
+ ## 2D and 3D Elements: slicing
85
+
86
+ For data defined in a 2D space, there are 2D equivalents of the 1D ``Curve`` and ``Scatter`` types. ``Points``, for example, can be thought of as a number of points in a 2D space.
87
+
88
+
89
+ ```python
90
+ r = np.arange(0, 1, 0.005)
91
+ xs, ys = (r * fn(85*np.pi*r) for fn in (np.cos, np.sin))
92
+ paths = hv.Points((xs, ys))
93
+ paths + paths[0:1, 0:1]
94
+ ```
95
+
96
+ However, indexing is not supported in this space, because there could be many possible points near a given set of coordinates, and finding the nearest one would require a search across potentially incommensurable dimensions, which is poorly defined and difficult to support.
97
+
98
+ Slicing in 3D works much like slicing in 2D, but indexing is not supported for the same reason as in 2D:
99
+
100
+
101
+ ```python
102
+ xs = np.linspace(0, np.pi*8, 201)
103
+ scatter = hv.Scatter3D((xs, np.sin(xs), np.cos(xs)))
104
+ layout = scatter + scatter[5:10, :, 0:]
105
+ hv.output(layout, backend='matplotlib')
106
+ ```
107
+
108
+ ## 2D Raster and Image: slicing and indexing
109
+
110
+ Raster and the various other image-like objects (Images, RGB, HSV, etc.) can all be sliced and indexed, as can Surface, because they all have an underlying regular grid of key dimension values:
111
+
112
+
113
+ ```python
114
+ np.random.seed(0)
115
+ extents = (0, 0, 10, 10)
116
+ img = hv.Image(np.random.rand(10, 10), bounds=extents)
117
+ img_slice = img[1:9,4:5]
118
+ box = hv.Bounds((1,4,9,5))
119
+ img*box + img_slice
120
+ ```
121
+
122
+
123
+ ```python
124
+ img[4.2,4.2], img[4.3,4.2], img[5.0,4.2]
125
+ ```
126
+
127
+ # Tabular indexing and slicing
128
+
129
+ While most indexing in HoloViews works by selecting the values along a dimension it is also frequently useful to index and slice using integer row and column indices. For this purpose most HoloViews objects have a ``.iloc`` indexing interface (mirroring the [pandas](http://pandas.pydata.org/pandas-docs/stable/indexing.html#different-choices-for-indexing) API), which supports all the usual indexing semantics. Supported iloc arguments include:
130
+
131
+ * An integer e.g. 5
132
+
133
+ * A list or array of integers [4, 3, 0]
134
+
135
+ * A slice object with ints 1:7
136
+
137
+ * A boolean array
138
+
139
+ #### Indexing
140
+
141
+ In this way we can for example select the x- and y-values in the 8th row of our ``Curve``:
142
+
143
+
144
+ ```python
145
+ xs = np.linspace(0, np.pi*2, 21)
146
+ curve = hv.Curve((xs, np.sin(xs)))
147
+ print('x: %s, y: %s' % (curve.iloc[8, 0], curve.iloc[8, 1]))
148
+ curve * hv.Scatter(curve.iloc[8])
149
+ ```
150
+
151
+ #### Slicing
152
+
153
+ Alternatively we can select every second sample between indices 5 and 16 of a ``Curve``:
154
+
155
+
156
+ ```python
157
+ curve + curve.iloc[5:16:2]
158
+ ```
159
+
160
+ #### Lists of integers and boolean indices
161
+
162
+ Finally we may also pass a list of the integer samples to select, or use boolean indices. This mode of indexing can be very useful for randomly sampling an Element or picking a specific set of rows or (columns):
163
+
164
+
165
+ ```python
166
+ curve.iloc[[0, 5, 10, 15, 20]] + curve.iloc[xs>3]
167
+ ```
168
+
169
+ # Sampling
170
+
171
+ Sampling is essentially a process of indexing an Element at multiple index locations, and collecting the results. Thus any Element that can be indexed can also be sampled. Compared to regular indexing, sampling is different in that multiple indices may be supplied at the same time. Also, indexing will only return the value at that location, whereas the return type from a sampling operation is another ``Element`` type, usually either a ``Table`` or a ``Curve``, to allow both key and value dimensions to be returned.
172
+
173
+ ### Sampling Elements
174
+
175
+ Sampling can use either an explicit list of indexes, or pass an index value for each dimension keyword argument.
176
+
177
+ We'll start by taking a single sample of an Image object, to make clear how sampling and indexing are similar operations yet different in their results:
178
+
179
+
180
+ ```python
181
+ img_coords = hv.Points(img, extents=extents)
182
+ labeled_img = img * img_coords * hv.Points([img.closest([(4.1,4.3)])]).opts(color='r')
183
+ img + labeled_img + img.sample([(4.1,4.3)])
184
+ ```
185
+
186
+
187
+ ```python
188
+ img[4.1,4.3]
189
+ ```
190
+
191
+ Here, the output of the indexing operation is the value (0.20887675609483469) from the location closest to the specified indexes, whereas ``.sample()`` returns a Table that lists both the coordinates *and* the value, and slicing (in previous section) returns an Element of the same type, not a Table.
192
+
193
+
194
+ Next we can try sampling along only one Dimension on our 2D Image, leaving us with a 1D Element (in this case a ``Curve``):
195
+
196
+
197
+ ```python
198
+ sampled = img.sample(y=5)
199
+ labeled_img = img * img_coords * hv.Points(zip(sampled['x'], [img.closest(y=5)]*10))
200
+ img + labeled_img + sampled
201
+ ```
202
+
203
+ Sampling works on any regularly sampled Element type. For example, we can select multiple samples along the x-axis of a Curve.
204
+
205
+
206
+ ```python
207
+ xs = np.arange(10)
208
+ samples = [2, 4, 6, 8]
209
+ curve = hv.Curve(zip(xs, np.sin(xs)))
210
+ curve_samples = hv.Scatter(zip(xs, [0] * 10)) * hv.Scatter(zip(samples, [0]*len(samples)))
211
+ curve + curve_samples + curve.sample(samples)
212
+ ```
213
+
214
+ ### Sampling HoloMaps
215
+
216
+ Sampling is often useful when you have more data than you wish to visualize or analyze at one time. First, let's create a HoloMap containing a number of observations of some noisy data.
217
+
218
+
219
+ ```python
220
+ obs_hmap = hv.HoloMap({i: hv.Image(np.random.randn(10, 10), bounds=extents)
221
+ for i in range(3)}, kdims='Observation')
222
+ ```
223
+
224
+ A `HoloMap` may not be sampled directly, instead we can use the `.apply` method to sample each element in the HoloMap and consequently use the `.collapse` method to produce a single `Dataset`. In this case we'll take 3x3 subsamples of each of the Images:
225
+
226
+
227
+ ```python
228
+ hv.output(backend='matplotlib', size=120)
229
+
230
+ sample_style = dict(edgecolors='k', alpha=1)
231
+ all_samples = obs_hmap.collapse().to.scatter3d().opts(alpha=0.15, xticks=4)
232
+ sampled = obs_hmap.apply.sample((3,3)).collapse()
233
+ subsamples = sampled.to.scatter3d().opts(**sample_style)
234
+ all_samples * subsamples + hv.Table(sampled)
235
+ ```
236
+
237
+ By supplying bounds in as a (left, bottom, right, top) tuple we can also sample a subregion of our images:
238
+
239
+
240
+ ```python
241
+ sampled = obs_hmap.apply.sample((3,3), bounds=(2,5,5,10)).collapse()
242
+ subsamples = sampled.to.scatter3d().opts(xticks=4, **sample_style)
243
+ all_samples * subsamples + hv.Table(sampled)
244
+ ```
245
+
246
+ Since this kind of sampling is only well supported for continuous coordinate systems, we can only apply this kind of sampling to Image types for now.
247
+
248
+ ### Sampling Charts
249
+
250
+ Sampling Chart-type Elements like Curve, Scatter, Histogram is only supported by providing an explicit list of samples, since those Elements have no underlying regular grid.
251
+
252
+
253
+ ```python
254
+ hv.output(backend='bokeh')
255
+
256
+ xs = np.arange(10)
257
+ extents = (0, 0, 2, 10)
258
+ curve = hv.HoloMap({(i) : hv.Curve(zip(xs, np.sin(xs)*i))
259
+ for i in np.linspace(0.5, 1.5, 3)},
260
+ kdims='Observation')
261
+ all_samples = curve.collapse().to.points()
262
+ sampled = curve.apply.sample([0, 2, 4, 6, 8]).collapse()
263
+ sample_points = sampled.to.points(extents=extents)
264
+ sampling = all_samples * sample_points.opts(color='red')
265
+ sampling + hv.Table(sampled)
266
+ ```
267
+
268
+ These tools should help you index, slice, sample, and select your data with ease. The [Tabular Data](./07-Tabular_Data.ipynb) guide explains how to do other types of operations, such as averaging and other reduction operations.
hvplot_docs/11-Transforming_Elements.md ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Applying Transformations
2
+
3
+
4
+ ```python
5
+ import param
6
+ import numpy as np
7
+ import holoviews as hv
8
+ from holoviews import opts
9
+
10
+ hv.extension('bokeh', 'matplotlib')
11
+ ```
12
+
13
+ HoloViews objects provide a convenient way of wrapping your data along with some metadata for exploration and visualization. For the simplest visualizations, you can simply declare a small collection of elements which can then be composed or placed in an appropriate container. As soon as the task becomes more complex, it is natural to write functions that output HoloViews objects.
14
+
15
+ In this user guide, we will introduce to related concepts to express transforms of some data, first we will cover `dim` transforms to express simple transforms of some data and then ``Operation`` classes to express more complex transformations. Operations provide a consistent structure for such code, making it possible to write general functions that can process HoloViews objects. This enables powerful new ways of specifying HoloViews objects computed from existing data, allowing the construction of flexible data processing pipelines. Examples of such operations are ``histogram``, ``rolling``, ``datashade`` or ``decimate``, which apply some computation on certain types of Element and return a new Element with the transformed data.
16
+
17
+ In this user guide we will discover how transforms and operations work, how to control their parameters and how to chain them. The [Data Processing Pipelines](./14-Data_Pipelines.ipynb) guide extends what we will have learned to demonstrate how operations can be applied lazily by using the ``dynamic`` flag, letting us define deferred processing pipelines that can drive highly complex visualizations and dashboards.
18
+
19
+ ## Transforms
20
+
21
+ A transform is expressed using a `dim` expression, which we originally introduced in the context of the [Style Mapping](./04-Style_Mapping.ipynb) user guide. It allows expressing some deferred computation on a HoloViews Element. This can be a powerful way to transform some
22
+ data quickly and easily. Let us start by declaring a `Dataset` with a single dimension `x`:
23
+
24
+
25
+ ```python
26
+ ds = hv.Dataset(np.linspace(0, np.pi), 'x')
27
+ ds
28
+ ```
29
+
30
+ The `Dataset` x values consist of an array of monotonically increasing values from 0 to np.pi. We can now define a transform which takes these values and transform them:
31
+
32
+
33
+ ```python
34
+ expr = np.sin(hv.dim('x')*10+5)
35
+ expr
36
+ ```
37
+
38
+ This expression takes these values multiplies them by 10, adds 5 and then calculates the `sine`. Using the `.transform` method we can now apply this expression to the `Dataset` and assign the values to a newly created `y` dimension by supplying it as a keyword (in the same way we could override the `x` dimension):
39
+
40
+
41
+ ```python
42
+ transformed = ds.transform(y=expr)
43
+ transformed
44
+ ```
45
+
46
+ We can see the result of this by casting it to a `Curve`:
47
+
48
+
49
+ ```python
50
+ hv.Curve(transformed)
51
+ ```
52
+
53
+ This allows almost any mathematical transformation to be expressed and applied on a `Dataset` in a deferred way. The regular `dim` expression supports all the standard mathematical operators and NumPy array methods. However if we want to use methods which exist only on specific datatypes we can invoke them using `.df` or `.xr`, which let you make (pandas) dataframe and xarray API (method and accessor) calls respectively. Let us for example load an XArray Dataset, which has a number of custom methods to perform complex computations on the data, e.g. the quantile method:
54
+
55
+
56
+ ```python
57
+ import xarray as xr
58
+
59
+ air_temp = xr.tutorial.load_dataset('air_temperature')
60
+ print(air_temp.quantile.__doc__)
61
+ ```
62
+
63
+ We can construct an expression to apply this method on the data and compute the 95th percentile of air temperatures along the 'time' dimension:
64
+
65
+
66
+ ```python
67
+ quantile_expr = hv.dim('air').xr.quantile(0.95, dim='time')
68
+ quantile_expr
69
+ ```
70
+
71
+ Now we can apply this to the `Dataset` using the `transform` method, in the resulting dataset we can see that the time dimension has been dropped:
72
+
73
+
74
+ ```python
75
+ temp_ds = hv.Dataset(air_temp, ['lon', 'lat', 'time'])
76
+
77
+ transformed_ds = temp_ds.transform(air=quantile_expr)
78
+
79
+ transformed_ds
80
+ ```
81
+
82
+ To visualize this data we will cast it to an `Image`:
83
+
84
+
85
+ ```python
86
+ hv.Image(transformed_ds)
87
+ ```
88
+
89
+ The real power of `dim` transforms comes in when combining them with parameters. We will look at this in more detail later as part of the [Pipeline user guide](14-Data_Pipelines.ipynb) but let's quickly see what this looks like. We will create a [Panel](https://panel.holoviz.org) slider to control the `q` value in the call to the `quantile` method:
90
+
91
+
92
+ ```python
93
+ import panel as pn
94
+
95
+ q = pn.widgets.FloatSlider(name='quantile')
96
+
97
+ quantile_expr = hv.dim('air').xr.quantile(q, dim='time')
98
+ quantile_expr
99
+ ```
100
+
101
+ Now that we have expressed this dynamic `dim` transform let us apply it using `.apply.transform`:
102
+
103
+
104
+ ```python
105
+ temp_ds = hv.Dataset(air_temp, ['lon', 'lat'])
106
+ transformed = temp_ds.apply.transform(air=quantile_expr).apply(hv.Image)
107
+
108
+ pn.Column(q, transformed.opts(colorbar=True, width=400))
109
+ ```
110
+
111
+ `dim` expressions provide a very powerful way to apply transforms on your data either statically or controlled by some external parameter, e.g. one driven by a Panel widget.
112
+
113
+ ## Operations are parameterized
114
+
115
+ In cases a simple transform is not sufficient or you want to encapsulate some transformation in a more rigorous way an `Operation` allows encapsulating the parameters of a transform on a function-like object. Operations in HoloViews are subclasses of ``Operation``, which transform one Element or ``Overlay`` of Elements by returning a new Element that may be a transformation of the original. All operations are parameterized using the [param](https://github.com/holoviz/param) library which allows easy validation and documentation of the operation arguments. In particular, operations are instances of ``param.ParameterizedFunction`` which allows operations to be used in the same way as normal python functions.
116
+
117
+ This approach has several advantages, one of which is that we can manipulate the parameters of operations at several different levels: at the class-level, at the instance-level or when it is called. Another advantage is that using parameterizing operations allows them to be inspected just like any other HoloViews object using ``hv.help``. We will now do this for the ``histogram`` operation:
118
+
119
+
120
+ ```python
121
+ from holoviews.operation import histogram
122
+ hv.help(histogram)
123
+ ```
124
+
125
+ ## Applying operations
126
+
127
+ Above we can see a listing of all the parameters of the operation, with the defaults, the expected types and detailed docstrings for each one. The ``histogram`` operation can be applied to any Element and will by default generate a histogram for the first value dimension defined on the object it is applied to. As a simple example we can create an ``BoxWhisker`` Element containing samples from a normal distribution, and then apply the ``histogram`` operation to those samples in two ways: 1) by creating an instance on which we will change the ``num_bins`` and 2) by passing ``bin_range`` directly when calling the operation:
128
+
129
+
130
+ ```python
131
+ boxw = hv.BoxWhisker(np.random.randn(10000))
132
+ histop_instance = histogram.instance(num_bins=50)
133
+
134
+ boxw + histop_instance(boxw).relabel('num_bins=50') + histogram(boxw, bin_range=(0, 3)).relabel('bin_range=(0, 3)')
135
+ ```
136
+
137
+ We can see that these two ways of using operations gives us convenient control over how the parameters are applied. An instance allows us to persist some defaults which will be used in all subsequent calls, while passing keyword arguments to the operations applies the parameters for just that particular call.
138
+
139
+ The third way to manipulate parameters is to set them at the class level. If we always want to use ``num_bins=30`` instead of the default of ``num_bins=20`` shown in the help output above, we can simply set ``histogram.num_bins=30``.
140
+
141
+ ## Operations on containers
142
+
143
+ ``Operations`` in HoloViews are applied to individual elements, which means that when you apply an operation to a container object (such as ``NdLayout``, ``GridSpace`` and ``HoloMap``) the operation is applied once per element. For an operation to work, all the elements must be of the same type which means the operation effectively acts to map the operation over all the contained elements. As a simple example we can define a HoloMap of ``BoxWhisker`` Elements by varying the width of the distribution via the ``Sigma`` value and then apply the histogram operation to it:
144
+
145
+
146
+ ```python
147
+ holomap = hv.HoloMap({(i*0.1+0.1): hv.BoxWhisker(np.random.randn(10000)*(i*0.1+0.1)) for i in range(5)},
148
+ kdims='Sigma')
149
+ holomap + histogram(holomap)
150
+ ```
151
+
152
+ As you can see the operation has generated a ``Histogram`` for each value of ``Sigma`` in the ``HoloMap``. In this way we can apply the operation to the entire parameter space defined by a ``HoloMap``, ``GridSpace``, and ``NdLayout``.
153
+
154
+ ## Combining operations
155
+
156
+ Since operations take a HoloViews object as input and return another HoloViews object we can very easily chain and combine multiple operations to perform complex analyses quickly and easily, while instantly visualizing the output.
157
+
158
+ In this example we'll work with operations on timeseries. We first define a small function to generate a random, noisy timeseries:
159
+
160
+
161
+ ```python
162
+ from holoviews.operation import timeseries
163
+
164
+ def time_series(T = 1, N = 100, mu = 0.1, sigma = 0.1, S0 = 20):
165
+ """Parameterized noisy time series"""
166
+ dt = float(T)/N
167
+ t = np.linspace(0, T, N)
168
+ W = np.random.standard_normal(size = N)
169
+ W = np.cumsum(W)*np.sqrt(dt) # standard brownian motion
170
+ X = (mu-0.5*sigma**2)*t + sigma*W
171
+ S = S0*np.exp(X) # geometric brownian motion
172
+ return S
173
+
174
+ curve = hv.Curve(time_series(N=1000)).opts(width=600)
175
+ ```
176
+
177
+ Now we will start applying some operations to this data. HoloViews ships with two ready-to-use timeseries operations: the ``rolling`` operation, which applies a function over a rolling window, and a ``rolling_outlier_std`` operation that computes outlier points in a timeseries by excluding points less than ``sigma`` standard deviation removed from the rolling mean:
178
+
179
+
180
+ ```python
181
+ smoothed = curve * timeseries.rolling(curve) * timeseries.rolling_outlier_std(curve)
182
+ smoothed.opts(opts.Scatter(color='black'))
183
+ ```
184
+
185
+ In the next section we will define a custom operation that will compose with the ``smoothed`` operation output above to form a short operation pipeline.
186
+
187
+ ## Defining custom operations
188
+
189
+ We can now define our own custom ``Operation`` which as you may recall can process either elements and overlays. This means we can define a simple operation that takes our ``smoothed`` overlay and computes the difference between the raw and smoothed curves that it contains. Such a subtraction will give us the residual between the smoothed and unsmoothed ``Curve`` elements, removing long-term trends and leaving the short-term variation.
190
+
191
+ Defining an operation is very simple. An ``Operation`` subclass should define a ``_process`` method, which simply accepts an ``element`` argument. Optionally we can also define parameters on the operation, which we can access using the ``self.p`` attribute on the operation. In this case we define a ``String`` parameter, which specifies the name of the subtracted value dimension on the returned Element.
192
+
193
+
194
+ ```python
195
+ from holoviews.operation import Operation
196
+
197
+ class residual(Operation):
198
+ """
199
+ Subtracts two curves from one another.
200
+ """
201
+
202
+ label = param.String(default='Residual', doc="""
203
+ Defines the label of the returned Element.""")
204
+
205
+ def _process(self, element, key=None):
206
+ # Get first and second Element in overlay
207
+ el1, el2 = element.get(0), element.get(1)
208
+
209
+ # Get x-values and y-values of curves
210
+ xvals = el1.dimension_values(0)
211
+ yvals = el1.dimension_values(1)
212
+ yvals2 = el2.dimension_values(1)
213
+
214
+ # Return new Element with subtracted y-values
215
+ # and new label
216
+ return el1.clone((xvals, yvals-yvals2),
217
+ vdims=self.p.label)
218
+ ```
219
+
220
+ Having defined the residual operation let's try it out right away by applying it to our original and smoothed ``Curve``. We'll place the two objects on top of each other so they can share an x-axis and we can compare them directly:
221
+
222
+
223
+ ```python
224
+ (smoothed + residual(smoothed).opts(xaxis=None)).cols(1)
225
+ ```
226
+
227
+ In this view we can immediately see that only a very small residual is left when applying this level of smoothing. However we have only tried one particular ``rolling_window`` value, the default value of ``10``. To assess how this parameter affects the residual we can evaluate the operation over a number different parameter settings, as we will see in the next section.
228
+
229
+ ## Evaluating operation parameters
230
+
231
+ When applying an operation there are often parameters to vary. Using traditional plotting approaches it's often difficult to evaluate them interactively to get a detailed understanding of what they do. Here we will apply the ``rolling`` operations with varying ``rolling_window`` widths and ``window_type``s across a ``HoloMap``:
232
+
233
+
234
+ ```python
235
+ rolled = hv.HoloMap({(w, str(wt)): timeseries.rolling(curve, rolling_window=w, window_type=wt)
236
+ for w in [10, 25, 50, 100, 200] for wt in [None, 'hamming', 'triang']},
237
+ kdims=['Window', 'Window Type'])
238
+ rolled
239
+ ```
240
+
241
+ This visualization is already useful since we can compare the effect of various parameter values by moving the slider and trying different window options. However since we can also chain operations we can easily compute the residual and view the two together.
242
+
243
+ To do this we simply overlay the ``HoloMap`` of smoothed curves on top of the original curve and pass it to our new ``residual`` function. Then we can combine the smoothed view with the original and see how the smoothing and residual curves vary across parameter values:
244
+
245
+
246
+ ```python
247
+ (curve * rolled + residual(curve * rolled)).cols(1)
248
+ ```
249
+
250
+ Using a few additional lines we have now evaluated the operation over a number of different parameters values, allowing us to process the data with different smoothing parameters. In addition, by interacting with this visualization we can gain a better understanding of the operation parameters as well as gain insights into the structure of the underlying data.
251
+
252
+ ## Operations on 2D elements
253
+
254
+ Let's look at another example of operations in action, this time applying a simple filter to an `Image`. The basic idea is the same as above, although accessing the values to be transformed is a bit more complicated. First, we prepare an example image:
255
+
256
+
257
+ ```python
258
+ hv.output(backend='matplotlib', size=200)
259
+
260
+ from scipy.misc import ascent
261
+
262
+ stairs_image = hv.Image(ascent()[200:500, :], bounds=[0, 0, ascent().shape[1], 300], label="stairs")
263
+ stairs_image
264
+ ```
265
+
266
+ We'll define a simple ``Operation``, which takes an ``Image`` and applies a high-pass or low-pass filter. We then use this to build a ``HoloMap`` of images filtered with different sigma values:
267
+
268
+
269
+ ```python
270
+ from scipy import ndimage
271
+
272
+ class image_filter(hv.Operation):
273
+
274
+ sigma = param.Number(default=5)
275
+
276
+ type_ = param.String(default="low-pass")
277
+
278
+ def _process(self, element, key=None):
279
+ xs = element.dimension_values(0, expanded=False)
280
+ ys = element.dimension_values(1, expanded=False)
281
+
282
+ # setting flat=False will preserve the matrix shape
283
+ data = element.dimension_values(2, flat=False)
284
+
285
+ if self.p.type_ == "high-pass":
286
+ new_data = data - ndimage.gaussian_filter(data, self.p.sigma)
287
+ else:
288
+ new_data = ndimage.gaussian_filter(data, self.p.sigma)
289
+
290
+ label = element.label + " ({} filtered)".format(self.p.type_)
291
+ # make an exact copy of the element with all settings, just with different data and label:
292
+ element = element.clone((xs, ys, new_data), label=label)
293
+ return element
294
+
295
+ stairs_map = hv.HoloMap({sigma: image_filter(stairs_image, sigma=sigma)
296
+ for sigma in range(0, 12, 1)}, kdims="sigma")
297
+
298
+ stairs_map.opts(framewise=True)
299
+ ```
300
+
301
+ Just as in the previous example, it is quite straight-forward to build a HoloMap containing results for different parameter values. Inside the ``_process()`` method, the given parameters can be accessed as ``self.p.<parameter-name>`` (note that ``self.<parameter_name>`` always contains the default value!). Since we did not specify the ``type_`` parameter, it defaulted to "low-pass".
302
+
303
+ There are some peculiarities when applying operations to two-dimensional elements:
304
+
305
+ - Understanding the ``dimension_values()`` method: In principle, one could use ``element.data`` to access the element's data, however, since HoloViews can wrap a wide range of data formats, ``dimension_values()`` provides an API that lets you access the data without having to worry about the type of the data. The first parameter specifies the dimension to be returned. On a 2D element like an Image or Raster the first two dimensions reference the key dimensions, so passing an index of 0 or 1 will return the x- and y-axis values respectively. Any subsequent dimensions will be value dimensions, e.g. on an Image index value 2 will refer to the intensity values and on an RGB index values 2, 3, and 4 will return the Red, Green and Blue intensities instead. Setting ``expanded=False`` yields only the axis, while the default setting ``expanded=True`` returns a value for every pixel. Specifying ``flat=False`` means that the data's matrix shape will be preserved, which is what we need for this kind of filter.
306
+ - ``Image`` and related classes come with convenient methods to convert between matrix indices and data coordinates and vice versa: ``matrix2sheet()`` and ``sheet2matrix()``. This is useful when searching for features such as peaks.
307
+
308
+ A very powerful aspect of operations is the fact that they understand Holoviews data structures. This means it is very straight-forward to apply an operation to every element in a container. As an example, let's apply an additional high-pass filter to our HoloMap:
309
+
310
+
311
+ ```python
312
+ image_filter(stairs_map, type_="high-pass").opts(framewise=True)
313
+ ```
314
+
315
+ Note, that the sigma value for the high-pass filter has defaulted to 5, and the sigma value in the HoloMap still corresponds to the original low-pass filter.
316
+
317
+
318
+ ## Benefits of using ``Operation``
319
+
320
+ Now that we have seen some operations in action we can get some appreciation of what makes them useful. When working with data interactively we often end up applying a lot of ad-hoc data transforms, which provides maximum flexibility but is neither reproducible nor maintainable. Operations allow us to encapsulate analysis code using a well defined interface that is well suited for building complex analysis pipelines:
321
+
322
+ 1. ``Operation`` parameters are well defined by declaring parameters on the class. These parameters can be easily documented and automatically carry out validation on the types and ranges of the inputs. These parameters are documented using ``hv.help``.
323
+
324
+ 2. Both inputs and outputs of an operation are instantly visualizable, because the data **is** the visualization. This means you're not constantly context switching between data processing and visualization --- visualization comes for free as you build your data processing pipeline.
325
+
326
+ 3. Operations understand HoloViews datastructures and can be immediately applied to any appropriate collection of elements, allowing you to evaluate the operation with permutations of parameter values. This flexibility makes it easy to assess the effect of operation parameters and their effect on your data.
327
+
328
+ 4. As we will discover in the [Data processing pipelines](./14-Data_Pipelines.ipynb) guide, operations can be applied lazily to build up complex deferred data-processing pipelines, which can aid your data exploration and drive interactive visualizations and dashboards.
329
+
330
+ ## Other types of operation
331
+
332
+ As we have seen ``Operation`` is defined at the level of processing HoloViews elements or overlays of elements. In some situations, you may want to compute a new HoloViews datastructure from a number of elements contained in a structure other than an overlay, such as a HoloMap or a Layout.
333
+
334
+ One such pattern is an operation that accepts and returns a ``HoloMap`` where each of the output element depends on all the data in the input ``HoloMap``. For situations such as these, subclassing ``Operation`` is not appropriate and we recommend defining your own function. These custom operation types won't automatically gain support for lazy pipelines as described in the [Data processing pipelines](./14-Data_Pipelines.ipynb) guide and how these custom operations are pipelined is left as a design decision for the user. Note that as long as these functions return simple elements or containers, their output can be used by subclasses of ``Operation`` as normal.
335
+
336
+ What we *do* recommend is that you subclass from ``param.ParameterizedFunction`` so that you can declare well-documented and validated parameters, add a description of your operation with a class level docstring and gain automatic documentation support via ``hv.help``.
hvplot_docs/12-Responding_to_Events.md ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Responding to Events
2
+
3
+
4
+ ```python
5
+ import numpy as np
6
+ import holoviews as hv
7
+ from holoviews import opts
8
+
9
+ hv.extension('bokeh')
10
+ ```
11
+
12
+ In the [Live Data](./07-Live_Data.ipynb) guide we saw how ``DynamicMap`` allows us to explore high dimensional data using the widgets in the same style as ``HoloMaps``. Although suitable for unbounded exploration of large parameter spaces, the ``DynamicMaps`` described in that notebook support exactly the same mode of interaction as ``HoloMaps``. In particular, the key dimensions are used to specify a set of widgets that when manipulated apply the appropriate indexing to invoke the user-supplied callable.
13
+
14
+ In this user guide we will explore the HoloViews streams system that allows *any* sort of value to be supplied from *anywhere*. This system opens a huge set of new possible visualization types, including continuously updating plots that reflect live data as well as dynamic visualizations that can be interacted with directly, as described in the [Custom Interactivity](./13-Custom_Interactivity.ipynb) guide.
15
+
16
+ <center><div class="alert alert-info" role="alert">To use visualize and use a <b>DynamicMap</b> you need to be running a live Jupyter server.<br>When viewing this user guide as part of the documentation DynamicMaps will be sampled with a limited number of states.<br></div></center>
17
+
18
+
19
+ ```python
20
+ # Styles and plot options used in this user guide
21
+
22
+ opts.defaults(
23
+ opts.Area(fill_color='cornsilk', line_width=2,
24
+ line_color='black'),
25
+ opts.Ellipse(bgcolor='white', color='black'),
26
+ opts.HLine(color='red', line_width=2),
27
+ opts.Image(cmap='viridis'),
28
+ opts.Path(bgcolor='white', color='black', line_dash='dashdot',
29
+ show_grid=False),
30
+ opts.VLine(color='red', line_width=2))
31
+ ```
32
+
33
+ ## A simple ``DynamicMap``
34
+
35
+ Before introducing streams, let us declare a simple ``DynamicMap`` of the sort discussed in the [Live Data](07-Live_Data.ipynb) user guide. This example consists of a ``Curve`` element showing a [Lissajous curve](https://en.wikipedia.org/wiki/Lissajous_curve) with ``VLine`` and ``HLine`` annotations to form a crosshair:
36
+
37
+
38
+ ```python
39
+ lin = np.linspace(-np.pi,np.pi,300)
40
+
41
+ def lissajous(t, a=3, b=5, delta=np.pi/2.):
42
+ return (np.sin(a * t + delta), np.sin(b * t))
43
+
44
+ def lissajous_crosshair(t, a=3, b=5, delta=np.pi/2):
45
+ (x,y) = lissajous(t,a,b,delta)
46
+ return hv.VLine(x) * hv.HLine(y)
47
+
48
+ crosshair = hv.DynamicMap(lissajous_crosshair, kdims='t').redim.range(t=(-3.,3.))
49
+
50
+ path = hv.Path(lissajous(lin))
51
+
52
+ path * crosshair
53
+ ```
54
+
55
+ As expected, the declared key dimension (``kdims``) has turned into a slider widget that lets us move the crosshair along the curve. Now let's see how to position the crosshair using streams.
56
+
57
+ ## Introducing streams
58
+
59
+
60
+
61
+ The core concept behind a stream is simple: it defines one or more parameters that can change over time that automatically refreshes code depending on those parameter values.
62
+
63
+ Like all objects in HoloViews, these parameters are declared using [param](https://param.holoviz.org/) and streams are defined as a parameterized subclass of the ``holoviews.streams.Stream``. A more convenient way is to use the ``Stream.define`` classmethod:
64
+
65
+
66
+ ```python
67
+ from holoviews.streams import Stream, param
68
+ Time = Stream.define('Time', t=0.0)
69
+ ```
70
+
71
+ This results in a ``Time`` class with a numeric ``t`` parameter that defaults to zero. As this object is parameterized, we can use ``hv.help`` to view its parameters:
72
+
73
+
74
+ ```python
75
+ hv.help(Time)
76
+ ```
77
+
78
+ This parameter is a ``param.Number`` as we supplied a float, if we had supplied an integer it would have been a ``param.Integer``. Notice that there is no docstring in the help output above but we can add one by explicitly defining the parameter as follows:
79
+
80
+
81
+ ```python
82
+ Time = Stream.define('Time', t=param.Number(default=0.0, doc='A time parameter'))
83
+ hv.help(Time)
84
+ ```
85
+
86
+ Now we have defined this ``Time`` stream class, we can make of an instance of it and look at its parameters:
87
+
88
+
89
+ ```python
90
+ time_dflt = Time()
91
+ print('This Time instance has parameter t={t}'.format(t=time_dflt.t))
92
+ ```
93
+
94
+ As with all parameterized classes, we can choose to instantiate our parameters with suitable values instead of relying on defaults.
95
+
96
+
97
+ ```python
98
+ time = Time(t=np.pi/4)
99
+ print('This Time instance has parameter t={t}'.format(t=time.t))
100
+ ```
101
+
102
+ For more information on defining ``Stream`` classes this way, use ``hv.help(Stream.define)``.
103
+
104
+ ### Simple streams example
105
+
106
+ We can now supply this streams object to a ``DynamicMap`` using the same ``lissajous_crosshair`` callback from above by adding it to the ``streams`` list:
107
+
108
+
109
+ ```python
110
+ dmap = hv.DynamicMap(lissajous_crosshair, streams=[time])
111
+ path * dmap + path * lissajous_crosshair(t=np.pi/4.)
112
+ ```
113
+
114
+ Immediately we see that the crosshair position of the ``DynamicMap`` reflects the ``t`` parameter values we set on the ``Time`` stream. This means that the ``t`` parameter was supplied as the argument to the ``lissajous_curve`` callback. As we now have no key dimensions, there is no longer a widget for the ``t`` dimensions.
115
+
116
+ Although we have what looks like a static plot, it is in fact dynamic and can be updated in place at any time. To see this, we can call the ``event`` method on our ``DynamicMap``:
117
+
118
+
119
+
120
+ ```python
121
+ dmap.event(t=0.2)
122
+ ```
123
+
124
+ Running this cell will have updated the crosshair from its original position where $t=\frac{\pi}{4}$ to a new position where ``t=0.2``. Try running the cell above with different values of ``t`` and watch the plot update!
125
+
126
+ This ``event`` method is the recommended way of updating the stream parameters on a ``DynamicMap`` but if you have a handle on the relevant stream instance, you can also call the ``event`` method on that:
127
+
128
+
129
+ ```python
130
+ time.event(t=-0.2)
131
+ ```
132
+
133
+ Running the cell above also moves the crosshair to a new position. As there are no key dimensions, there is only a single valid (empty) key that can be accessed with ``dmap[()]`` or ``dmap.select()`` making ``event`` the only way to explore new parameters.
134
+
135
+ We will examine the ``event`` method and the machinery that powers streams in more detail later in the user guide after we have looked at more examples of how streams are used in practice.
136
+
137
+ ### Working with multiple streams
138
+
139
+ The previous example showed a curve parameterized by a single dimension ``t``. Often you will have multiple stream parameters you would like to declare as follows:
140
+
141
+
142
+ ```python
143
+ ls = np.linspace(0, 10, 200)
144
+ xx, yy = np.meshgrid(ls, ls)
145
+
146
+ XY = Stream.define('XY',x=0.0,y=0.0)
147
+
148
+ def marker(x,y):
149
+ return hv.VLine(x) * hv.HLine(y)
150
+
151
+ image = hv.Image(np.sin(xx)*np.cos(yy))
152
+
153
+ dmap = hv.DynamicMap(marker, streams=[XY()])
154
+
155
+ image * dmap
156
+ ```
157
+
158
+ You can update both ``x`` and ``y`` by passing multiple keywords to the ``event`` method:
159
+
160
+
161
+ ```python
162
+ dmap.event(x=-0.2, y=0.1)
163
+ ```
164
+
165
+ Note that the definition above behaves the same as the following definition where we define separate ``X`` and ``Y`` stream classes:
166
+
167
+ ```python
168
+ X = Stream.define('X',x=0.0)
169
+ Y = Stream.define('Y',y=0.0)
170
+ hv.DynamicMap(marker, streams=[X(), Y()])
171
+ ```
172
+
173
+ The reason why you might want to list multiple streams instead of always defining a single stream containing all the required stream parameters will be made clear in the [Custom Interactivity](./13-Custom_Interactivity.ipynb) guide.
174
+
175
+ ## Using Parameterized classes as a stream
176
+
177
+ Creating a custom ``Stream`` class is one easy way to declare parameters. However, there's no need to make a Stream if you have already expressed your domain knowledge on a ``Parameterized`` class. For instance, let's assume you have made a simple parameterized `BankAccount` class:
178
+
179
+
180
+
181
+ ```python
182
+ class BankAccount(param.Parameterized):
183
+ balance = param.Number(default=0, doc="Bank balance in USD")
184
+ overdraft = param.Number(default=200, doc="Overdraft limit")
185
+
186
+ account = BankAccount(name='Jane', balance=300)
187
+ ```
188
+
189
+ You can link parameter changes straight to DynamicMap callable parameters by passing a keyword:param dictionary to the `streams` argument (for HoloViews version >= 1.14.2):
190
+
191
+
192
+ ```python
193
+ streams = dict(total=account.param.balance, overdraft=account.param.overdraft, owner=account.param.name)
194
+
195
+ def table(owner, total, overdraft):
196
+ return hv.Table([(owner, overdraft, total)], ['Owner', 'Overdraft ($)', 'Total ($)'])
197
+
198
+ bank_dmap = hv.DynamicMap(table, streams=streams)
199
+ bank_dmap.opts(height=100)
200
+ ```
201
+
202
+ Now as you set the `balance` parameter on the `janes_account` instance, the DynamicMap above updates. Note that the dictionary specifies that the `balance` parameter is mapped to the `total` argument of the callable.
203
+
204
+
205
+ ```python
206
+ account.balance=65.4
207
+ account.overdraft=350
208
+ ```
209
+
210
+ ### Use with `panel`
211
+
212
+ This dictionary format is particularly useful when used with the [Panel](http://panel.pyviz.org/) library (a dependency of HoloViews that should always be available), because `panel` widgets always reflect their values on the `value` parameter. This means that if you declare two Panel widgets as follows:
213
+
214
+
215
+ ```python
216
+ import panel as pn
217
+
218
+ slider = pn.widgets.FloatSlider(start=0, end=500, name='Balance')
219
+ checkbox = pn.widgets.Select(options=['student','regular', 'savings'], name='Account Type')
220
+ pn.Row(slider, checkbox)
221
+ ```
222
+
223
+ You can map both widget values into a `DynamicMap` callback without having a name clash as follows:
224
+
225
+
226
+ ```python
227
+ overdraft_limits = {'student':300, 'regular':100, 'savings':0} # Overdraft limits for different account types
228
+ streams = dict(owner=account.param.name, total=slider.param.value, acc=checkbox.param.value)
229
+
230
+ def account_info(owner, total, acc):
231
+ return hv.Table([(owner, acc, overdraft_limits[acc], total)],
232
+ ['Owner', 'Account Type', 'Overdraft ($)', 'Total ($)'])
233
+
234
+ widget_dmap = hv.DynamicMap(account_info, streams=streams)
235
+ widget_dmap.opts(height=100)
236
+ ```
237
+
238
+
239
+ You can now update the plot above using the slider and dropdown widgets. Note that for all these examples, a `Params` stream is created internally. This type of stream can wrap Parameterized objects or sets of Parameters but (since HoloViews 1.10.8) it is rare that an explicit stream object like that needs to be used directly at the user level. To see more examples of how to use Panel with HoloViews, see the [Dashboards user guide](./16-Dashboards.ipynb).
240
+
241
+ ### Using `.apply.opts`
242
+
243
+ You can supplying Parameters in a similar manner to the `.apply.opts` method. In the following example, a `Style` class has Parameters that indicate the desired colorma and color levels for the `image` instance defined earlier. We can link these together as follows:
244
+
245
+
246
+ ```python
247
+ class Style(param.Parameterized):
248
+
249
+ colormap = param.ObjectSelector(default='viridis', objects=['viridis', 'plasma', 'magma'])
250
+
251
+ color_levels = param.Integer(default=255, bounds=(1, 255))
252
+
253
+ style = Style()
254
+ image.apply.opts(colorbar=True, width=400, cmap=style.param.colormap, color_levels=style.param.color_levels)
255
+ ```
256
+
257
+ Using the `.apply` accessor in this automatically makes the resulting `DynamicMap` depend on the streams specified by the Parameters. Unlike a regular streams class, the plot will update whenever a Parameter on the instance or class changes. For instance, we can update the ``cmap`` and ``color_level`` parameters and watch the plot update in response:
258
+
259
+
260
+ ```python
261
+ style.color_levels = 10
262
+ style.colormap = 'plasma' # Note that this is mapped to the 'cmap' keyword in .apply.opts
263
+ ```
264
+
265
+ ## Combining streams and key dimensions
266
+
267
+
268
+ All the ``DynamicMap`` examples above can't be indexed with anything other than ``dmap[()]`` or ``dmap.select()`` as none of them had any key dimensions. This was to focus exclusively on the streams system at the start of the user guide and not because you can't combine key dimensions and streams:
269
+
270
+
271
+ ```python
272
+ xs = np.linspace(-3, 3, 400)
273
+
274
+ def function(xs, time):
275
+ "Some time varying function"
276
+ return np.exp(np.sin(xs+np.pi/time))
277
+
278
+ def integral(limit, time):
279
+ curve = hv.Curve((xs, function(xs, time)))[limit:]
280
+ area = hv.Area ((xs, function(xs, time)))[:limit]
281
+ summed = area.dimension_values('y').sum() * 0.015 # Numeric approximation
282
+ return (area * curve * hv.VLine(limit) * hv.Text(limit + 0.5, 2.0, '%.2f' % summed))
283
+
284
+ Time = Stream.define('Time', time=1.0)
285
+ dmap = hv.DynamicMap(integral, kdims='limit', streams=[Time()]).redim.range(limit=(-3,2))
286
+ dmap
287
+ ```
288
+
289
+ In this example, you can drag the slider to see a numeric approximation to the integral on the left side on the ``VLine``.
290
+
291
+ As ``'limit'`` is declared as a key dimension, it is given a normal HoloViews slider. As we have also defined a ``time`` stream, we can update the displayed curve for any time value:
292
+
293
+
294
+ ```python
295
+ dmap.event(time=8)
296
+ ```
297
+
298
+ We now see how to control the ``time`` argument of the integral function by triggering an event with a new time value, and how to control the ``limit`` argument by moving a slider. Controlling ``limit`` with a slider this way is valid but also a little unintuitive: what if you could control ``limit`` just by hovering over the plot?
299
+
300
+ In the [Custom Interactivity](13-Custom_Interactivity.ipynb) user guide, we will see how we can do exactly this by switching to the bokeh backend and using the linked streams system.
301
+
302
+ ### Matching names to arguments
303
+
304
+ Note that in the example above, the key dimension names and the stream parameter names match the arguments to the callable. This *must* be true for stream parameters but this isn't a requirement for key dimensions: if you replace the word 'radius' with 'size' in the example above after ``XY`` is defined, the example still works.
305
+
306
+ Here are the rules regarding the callback argument names:
307
+
308
+ * If your key dimensions and stream parameters match the callable argument names, the definition is valid.
309
+ * If your callable accepts mandatory positional arguments and their number matches the number of key dimensions, the names don't need to match and these arguments will be passed key dimensions values.
310
+
311
+ As stream parameters always need to match the argument names, there is a method to allow them to be easily renamed. Let's say you imported a stream class as shown in [Custom_Interactivity](13-Custom_Interactivity.ipynb) or for this example, reuse the existing ``XY`` stream class. You can then use the ``rename`` method allowing the following definition:
312
+
313
+
314
+ ```python
315
+ def integral2(lim, t):
316
+ 'Same as integral with different argument names'
317
+ return integral(lim, t)
318
+
319
+ dmap = hv.DynamicMap(integral2, kdims='limit', streams=[Time().rename(time='t')]).redim.range(limit=(-3.,3.))
320
+ dmap
321
+ ```
322
+
323
+ Occasionally, it is useful to suppress some of the stream parameters of a stream class, especially when using the *linked streams* described in [Custom_Interactivity](13-Custom_Interactivity.ipynb). To do this you can rename the stream parameter to ``None`` so that you no longer need to worry about it being passed as an argument to the callable. To re-enable a stream parameter, it is sufficient to either give the stream parameter its original string name or a new string name.
324
+
325
+ ## Overlapping stream and key dimensions
326
+
327
+ In the above example above, the stream parameters do not overlap with the declared key dimension. What happens if we add 'time' to the declared key dimensions?
328
+
329
+
330
+
331
+ ```python
332
+ dmap=hv.DynamicMap(integral, kdims=['time','limit'], streams=[Time()]).redim.range(limit=(-3.,3.))
333
+ dmap
334
+ ```
335
+
336
+ First you might notice that the 'time' value is now shown in the title but that there is no corresponding time slider as its value is supplied by the stream.
337
+
338
+ The 'time' parameter is now an instance of what are called 'dimensioned streams' which re-enable indexing of these dimensions:
339
+
340
+
341
+ ```python
342
+ dmap[1,0] + dmap.select(time=3,limit=1.5) + dmap[None,1.5]
343
+ ```
344
+
345
+ In **A**, we supply our own values for the 'time and 'limit' parameters. This doesn't change the values of the 'time' parameters on the stream itself but it does allow us to see what would happen when the time value is one. Note the use of ``None`` in **C** as a way of leaving an explicit value unspecified, allowing the current stream value to be used.
346
+
347
+ This is one good reason to use dimensioned streams - it restores access to convenient indexing and selecting operation as a way of exploring your visualizations. The other reason it is useful is that if you keep all your parameters dimensioned, it re-enables the ``DynamicMap`` cache described in the [Live Data](07-Live_Data.ipynb), allowing you to record your interaction with streams and allowing you to cast to ``HoloMap`` for export:
348
+
349
+
350
+ ```python
351
+ dmap.reset() # Reset the cache, we don't want the values from the cell above
352
+ # TODO: redim the limit dimension to a default of 0
353
+ dmap.event(time=1)
354
+ dmap.event(time=1.5)
355
+ dmap.event(time=2)
356
+ hv.HoloMap(dmap)
357
+ ```
358
+
359
+ One use of this would be to have a simulator drive a visualization forward using ``event`` in a loop. You could then stop your simulation and retain the recent history of the output as long as the allowed ``DynamicMap`` cache.
360
+
361
+ ## Generators and argument-free callables
362
+
363
+ In addition to callables, Python supports [generators](https://docs.python.org/3/glossary.html#term-generator) that can be defined with the ``yield`` keyword. Calling a function that uses yield returns a [generator iterator](https://docs.python.org/3/glossary.html#term-generator-iterator) object that accepts no arguments but returns new values when iterated or when ``next()`` is applied to it.
364
+
365
+ HoloViews supports Python generators for completeness and [generator expressions](https://docs.python.org/3/glossary.html#term-generator-expression) can be a convenient way to define code inline instead of using lambda functions. As generators expressions don't accept arguments and can get 'exhausted' ***we recommend using callables with ``DynamicMap``*** - exposing the relevant arguments also exposes control over your visualization.
366
+
367
+ Unlike generators, callables that have arguments allow you to re-visit portions of your parameter space instead of always being forced in one direction via calls to ``next()``. With this caveat in mind, here is an example of a generator and the corresponding generator iterator that returns a ``BoxWhisker`` element:
368
+
369
+
370
+ ```python
371
+ def sample_distributions(samples=10, tol=0.04):
372
+ np.random.seed(42)
373
+ while True:
374
+ gauss1 = np.random.normal(size=samples)
375
+ gauss2 = np.random.normal(size=samples)
376
+ data = (['A']*samples + ['B']*samples, np.hstack([gauss1, gauss2]))
377
+ yield hv.BoxWhisker(data, 'Group', 'Value')
378
+ samples+=1
379
+
380
+ sample_generator = sample_distributions()
381
+ ```
382
+
383
+ This returns two box whiskers representing samples from two Gaussian distributions of 10 samples. Iterating over this generator simply resamples from these distributions using an additional sample each time.
384
+
385
+ As with a callable, we can pass our generator iterator to ``DynamicMap``:
386
+
387
+
388
+ ```python
389
+ hv.DynamicMap(sample_generator)
390
+ ```
391
+
392
+ Without using streams, we now have a problem as there is no way to trigger the generator to view the next distribution in the sequence. We can solve this by defining a stream with no parameters:
393
+
394
+
395
+ ```python
396
+ dmap = hv.DynamicMap(sample_generator, streams=[Stream.define('Next')()])
397
+ dmap
398
+ ```
399
+
400
+ ### Stream event update loops
401
+
402
+ Now we can simply use ``event()`` to drive the generator forward and update the plot, showing how the two Gaussian distributions converge as the number of samples increase.
403
+
404
+
405
+ ```python
406
+ for i in range(40):
407
+ dmap.event()
408
+ ```
409
+
410
+ Note that there is a better way to run loops that drive ``dmap.event()`` which supports a ``period`` (in seconds) between updates and a ``timeout`` argument (also in seconds):
411
+
412
+
413
+ ```python
414
+ dmap.periodic(0.1, 1000, timeout=3)
415
+ ```
416
+
417
+ In this generator example, ``event`` does not require any arguments but you can set the ``param_fn`` argument to a callable that takes an iteration counter and returns a dictionary for setting the stream parameters. In addition you can use ``block=False`` to avoid blocking the notebook using a threaded loop. This can be very useful although it has two downsides 1. all running visualizations using non-blocking updates will be competing for computing resources 2. if you override a variable that the thread is actively using, there can be issues with maintaining consistent state in the notebook.
418
+
419
+ Generally, the ``periodic`` utility is recommended for all such event update loops and it will be used instead of explicit loops in the rest of the user guides involving streams.
420
+
421
+
422
+ ### Using ``next()``
423
+
424
+ The approach shown above of using an empty stream works in an exactly analogous fashion for callables that take no arguments. In both cases, the ``DynamicMap`` ``next()`` method is enabled:
425
+
426
+
427
+ ```python
428
+ hv.HoloMap({i:next(dmap) for i in range(10)}, kdims='Iteration')
429
+ ```
430
+
431
+ ## Next steps
432
+
433
+ The streams system allows you to update plots in place making it possible to build live visualizations that update in response to incoming live data or any other type of event. As we have seen in this user guide, you can use streams together with key dimensions to add additional interactivity to your plots while retaining the familiar widgets.
434
+
435
+ This user guide used examples that work with either the matplotlib or bokeh backends. In the [Custom Interactivity](13-Custom_Interactivity.ipynb) user guide, you will see how you can directly interact with dynamic visualizations when using the bokeh backend.
436
+
437
+ ## [Advanced] How streams work
438
+
439
+
440
+
441
+ This optional section is not necessary for users who simply want to use the streams system, but it does describe how streams actually work in more detail.
442
+
443
+ A stream class is one that inherits from ``Stream`` that typically defines some new parameters. We have already seen one convenient way of defining a stream class:
444
+
445
+
446
+ ```python
447
+ defineXY = Stream.define('defineXY', x=0.0, y=0.0)
448
+ ```
449
+
450
+ This is equivalent to the following definition which would be more appropriate in library code or for complex stream class requiring lots of parameters that need to be documented:
451
+
452
+
453
+ ```python
454
+ class XY(Stream):
455
+ x = param.Number(default=0.0, constant=True, doc='An X position.')
456
+ y = param.Number(default=0.0, constant=True, doc='A Y position.')
457
+ ```
458
+
459
+ As we have already seen, we can make an instance of ``XY`` with some initial values for ``x`` and ``y``.
460
+
461
+
462
+ ```python
463
+ xy = XY(x=2,y=3)
464
+ ```
465
+
466
+ However, trying to modify these parameters directly will result in an exception as they have been declared constant (e.g ``xy.x=4`` will throw an error). This is because there are two allowed ways of modifying these parameters, the simplest one being ``update``:
467
+
468
+
469
+ ```python
470
+ xy.update(x=4,y=50)
471
+ xy.rename(x='xpos', y='ypos').contents
472
+ ```
473
+
474
+ This shows how you can update the parameters and also shows the correct way to view the stream parameter values via the ``contents`` property as this will apply any necessary renaming.
475
+
476
+ So far, using ``update`` has done nothing but force us to access parameter a certain way. What makes streams work are the side-effects you can trigger when changing a value via the ``event`` method. The relevant side-effect is to invoke callables called 'subscribers'
477
+
478
+ ### Subscribers
479
+
480
+ Without defining any subscribes, the ``event`` method is identical to ``update``:
481
+
482
+
483
+ ```python
484
+ xy = XY()
485
+ xy.event(x=4,y=50)
486
+ xy.contents
487
+ ```
488
+
489
+ Now let's add a subscriber:
490
+
491
+
492
+ ```python
493
+ def subscriber(xpos,ypos):
494
+ print('The subscriber received xpos={xpos} and ypos={ypos}'.format(xpos=xpos,ypos=ypos))
495
+
496
+ xy = XY().rename(x='xpos', y='ypos')
497
+ xy.add_subscriber(subscriber)
498
+ xy.event(x=4,y=50)
499
+ ```
500
+
501
+ As we can see, now when you call ``event``, our subscriber is called with the updated parameter values, renamed as appropriate. The ``event`` method accepts the original parameter names and the subscriber receives the new values after any renaming is applied. You can add as many subscribers as you want and you can clear them using the ``clear`` method:
502
+
503
+
504
+ ```python
505
+ xy.clear()
506
+ xy.event(x=0,y=0)
507
+ ```
508
+
509
+ When you define a ``DynamicMap`` using streams, the HoloViews plotting system installs the necessary callbacks as subscribers to update the plot when the stream parameters change. The above example clears all subscribers (it is equivalent to ``clear('all')``. To clear only the subscribers you define yourself use ``clear('user')`` and to clear any subscribers installed by the HoloViews plotting system use ``clear('internal')``.
510
+
511
+ When using linked streams as described in the [Custom Interactivity](13-Custom_Interactivity.ipynb) user guide, the plotting system recognizes the stream class and registers the necessary machinery with Bokeh to update the stream values based on direct interaction with the plot.
hvplot_docs/13-Custom_Interactivity.md ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Custom Interactivity
2
+
3
+
4
+ ```python
5
+ import param
6
+ import numpy as np
7
+ import holoviews as hv
8
+ from holoviews import opts
9
+
10
+ hv.extension('bokeh', 'matplotlib')
11
+ ```
12
+
13
+ In previous notebooks we discovered how the ``DynamicMap`` class allows us to declare objects in a lazy way to enable exploratory analysis of large parameter spaces. In the [Responding to Events](./12-Responding_to_Events.ipynb) guide we learned how to interactively push updates to existing plots by declaring Streams on a DynamicMap. In this user guide 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 seamless data exploration.
14
+
15
+ Some of the possibilities this opens up include:
16
+
17
+ * Dynamically aggregating datasets of billions of datapoints depending on the plot axis ranges using the [datashader](./15-Large_Data.ipynb) library.
18
+ * Responding to ``Tap`` and ``DoubleTap`` events to reveal more information in subplots.
19
+ * Computing statistics in response to selections applied with box- and lasso-select tools.
20
+
21
+ Currently only the bokeh backend for HoloViews supports the linked streams system but the principles used should extend to any backend that can define callbacks that fire when a user zooms or pans or interacts with a plot.
22
+
23
+ <center><div class="alert alert-info" role="alert">To use and visualize <b>DynamicMap</b> with linked <b>Stream</b> objects you need to be running a live Jupyter server.<br>This user guide assumes that it will be run in a live notebook environment.<br>
24
+ When viewed statically, DynamicMaps on this page will only show the first available Element.<br></div></center>
25
+
26
+ ## Available Linked Streams
27
+
28
+ 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.
29
+
30
+ Here is the full list of linked Stream that are all descendants of the ``LinkedStream`` baseclass:
31
+
32
+
33
+ ```python
34
+ from holoviews import streams
35
+ listing = ', '.join(sorted([str(s.name) for s in param.descendents(streams.LinkedStream)]))
36
+ print('The linked stream classes supported by HoloViews are:\n\n{listing}'.format(listing=listing))
37
+ ```
38
+
39
+ 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. Finally there are a number of drawing/editing streams such as ``BoxEdit``, ``PointDraw``, ``FreehandDraw``, ``PolyDraw`` and ``PolyEdit``.
40
+
41
+ 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 future guides.
42
+
43
+ ## Linking streams to plots
44
+
45
+ At the end of the [Responding to Events](./12-Responding_to_Events.ipynb) guide we discovered that streams have ``subscribers``, which allow defining user defined callbacks on events, but also allow HoloViews to install subscribers that let plots respond to Stream updates. Linked streams add another concept on top of ``subscribers``, namely the Stream ``source``.
46
+
47
+ 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.
48
+
49
+ 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``.
50
+
51
+
52
+ ```python
53
+ pointer = streams.PointerXY()
54
+ print(pointer.source)
55
+ ```
56
+
57
+ 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:
58
+
59
+
60
+ ```python
61
+ print('The %s stream has contents %r' % (pointer, pointer.contents))
62
+ ```
63
+
64
+ #### Automatic linking
65
+
66
+ 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``:
67
+
68
+
69
+ ```python
70
+ pointer_dmap = hv.DynamicMap(lambda x, y: hv.Points([(x, y)]), streams=[pointer])
71
+ print(pointer.source is pointer_dmap)
72
+ ```
73
+
74
+ 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:
75
+
76
+
77
+ ```python
78
+ pointer_dmap.opts(size=10)
79
+ ```
80
+
81
+ 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:
82
+
83
+
84
+ ```python
85
+ pointer.contents
86
+ ```
87
+
88
+ In the [Responding to Events](12-Responding_to_Events.ipynb) user guide, we introduced an integration example that would work more intuitively with linked streams. Here it is again with the ``limit`` value controlled by the ``PointerX`` linked stream:
89
+
90
+
91
+ ```python
92
+ xs = np.linspace(-3, 3, 400)
93
+
94
+ def function(xs, time):
95
+ "Some time varying function"
96
+ return np.exp(np.sin(xs+np.pi/time))
97
+
98
+ def integral(limit, time):
99
+ limit = -3 if limit is None else np.clip(limit,-3,3)
100
+ curve = hv.Curve((xs, function(xs, time)))[limit:]
101
+ area = hv.Area ((xs, function(xs, time)))[:limit]
102
+ summed = area.dimension_values('y').sum() * 0.015 # Numeric approximation
103
+ return (area * curve * hv.VLine(limit) * hv.Text(limit + 0.8, 2.0, '%.2f' % summed))
104
+
105
+ integral_streams = [
106
+ streams.Stream.define('Time', time=1.0)(),
107
+ streams.PointerX().rename(x='limit')]
108
+
109
+ integral_dmap = hv.DynamicMap(integral, streams=integral_streams)
110
+
111
+ integral_dmap.opts(
112
+ opts.Area(color='#fff8dc', line_width=2),
113
+ opts.Curve(color='black'),
114
+ opts.VLine(color='red'))
115
+ ```
116
+
117
+ We only needed to import and use the ``PointerX`` stream and rename the ``x`` parameter that tracks the cursor position to 'limit' so that it maps to the corresponding argument. Otherwise, the example only required bokeh specific style options to match the matplotlib example as closely as possible.
118
+
119
+ #### Explicit linking
120
+
121
+ 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 source explicitly. Here we will create a 2D ``Image`` of sine gratings, and then declare that this image is 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:
122
+
123
+
124
+ ```python
125
+ xvals = np.linspace(0,4,202)
126
+ ys,xs = np.meshgrid(xvals, -xvals[::-1])
127
+ img = hv.Image(np.sin(((ys)**3)*xs))
128
+
129
+ pointer = streams.PointerXY(x=0,y=0, source=img)
130
+ pointer_dmap = hv.DynamicMap(lambda x, y: hv.Points([(x, y)]), streams=[pointer])
131
+ ```
132
+
133
+ 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 over the image on the left:
134
+
135
+
136
+ ```python
137
+ img + pointer_dmap.opts(size=10, xlim=(-.5, .5), ylim=(-.5, .5))
138
+ ```
139
+
140
+ 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.
141
+
142
+ 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:
143
+
144
+
145
+ ```python
146
+ x_sample = hv.DynamicMap(lambda x, y: img.sample(x=np.clip(x,-.49,.49)), streams=[pointer])
147
+ y_sample = hv.DynamicMap(lambda x, y: img.sample(y=np.clip(y,-.49,.49)), streams=[pointer])
148
+
149
+ (x_sample + y_sample).opts(opts.Curve(framewise=True))
150
+ ```
151
+
152
+ 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.
153
+
154
+ #### Unlinking objects
155
+
156
+ Sometimes we just want to display an object designated as a source without linking it to the stream. If the object is not a ``DynamicMap``, like the ``Image`` we designated as a ``source`` above, we can make a copy of the object using the ``clone`` method. We can do the same with ``DynamicMap`` though we just need to supply ``link_inputs=False`` as an extra argument.
157
+
158
+ Here we will create a ``DynamicMap`` that draws a cross-hair at the cursor position:
159
+
160
+
161
+ ```python
162
+ pointer = streams.PointerXY(x=0, y=0)
163
+ cross_dmap = hv.DynamicMap(lambda x, y: (hv.VLine(x) * hv.HLine(y)), streams=[pointer])
164
+ ```
165
+
166
+ Now we will add two copies of the ``cross_dmap`` into a Layout but the subplot on the right will not be linking the inputs. Try hovering over the two subplots and observe what happens:
167
+
168
+
169
+ ```python
170
+ cross_dmap + cross_dmap.clone(link=False)
171
+ ```
172
+
173
+ Notice how hovering over the left plot updates the crosshair position on both subplots, while hovering over the right subplot has no effect.
174
+
175
+ ## Transient linked streams
176
+
177
+ In the basic [Responding to Events](12-Responding_to_Events.ipynb) user guide we saw that stream parameters can be updated and those values are then passed to the callback. This model works well for many different types of streams that have well-defined values at all times.
178
+
179
+ This approach is not suitable for certain events which only have a well defined value at a particular point in time. For instance, when you hover your mouse over a plot, the hover position always has a well-defined value but the click position is only defined when a click occurs (if it occurs).
180
+
181
+ This latter case is an example of what are called 'transient' streams. These streams are supplied new values only when they occur and fall back to a default value at all other times. This default value is typically ``None`` to indicate that the event is not occurring and therefore has no data.
182
+
183
+
184
+ Transient streams are particularly useful when you are subscribed to multiple streams, some of which are only occasionally triggered. A good example are the ``Tap`` and ``DoubleTap`` streams; while you sometimes just want to know the last tapped position, we can only tell the two events apart if their values are ``None`` when not active.
185
+
186
+ We'll start by declaring a ``SingleTap`` and a ``DoubleTap`` stream as ``transient``. Since both streams supply 'x' and 'y' parameters, we will rename the ``DoubleTap`` parameters to 'x2' and 'y2'.
187
+
188
+
189
+ ```python
190
+ tap = streams.SingleTap(transient=True)
191
+ double_tap = streams.DoubleTap(rename={'x': 'x2', 'y': 'y2'}, transient=True)
192
+ ```
193
+
194
+ Next we define a list of taps we can append to, and a function that accumulates the tap and double tap coordinates along with the number of taps, returning a ``Points`` Element of the tap positions.
195
+
196
+
197
+ ```python
198
+ taps = []
199
+
200
+ def record_taps(x, y, x2, y2):
201
+ if None not in [x,y]:
202
+ taps.append((x, y, 1))
203
+ elif None not in [x2, y2]:
204
+ taps.append((x2, y2, 2))
205
+ return hv.Points(taps, vdims='Taps')
206
+ ```
207
+
208
+ Finally we can create a ``DynamicMap`` from our callback and attach the streams. We also apply some styling so the points are colored depending on the number of taps.
209
+
210
+
211
+ ```python
212
+ taps_dmap = hv.DynamicMap(record_taps, streams=[tap, double_tap])
213
+
214
+ taps_dmap.opts(color='Taps', cmap={1: 'red', 2: 'gray'}, size=10, tools=['hover'])
215
+ ```
216
+
217
+ Now try single- and double-tapping within the plot area, each time you tap a new point is appended to the list and displayed. Single taps show up in red and double taps show up in grey. We can also inspect the list of taps directly:
218
+
219
+
220
+ ```python
221
+ taps
222
+ ```
hvplot_docs/14-Data_Pipelines.md ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Data Processing Pipelines
2
+
3
+
4
+ ```python
5
+ import pandas as pd
6
+ import holoviews as hv
7
+
8
+ from holoviews import opts
9
+ from bokeh.sampledata import stocks
10
+ from holoviews.operation.timeseries import rolling, rolling_outlier_std
11
+
12
+ hv.extension('bokeh')
13
+
14
+ opts.defaults(opts.Curve(width=600, framewise=True))
15
+ ```
16
+
17
+ In the previous guides we discovered how to load and declare [dynamic, live data](./07-Live_Data.ipynb) and how to [transform elements](./11-Transforming_Elements.ipynb) using `dim` expressions and operations. In this guide we will discover how to combine dynamic data with operations to declare lazy and declarative data processing pipelines, which can be used for interactive exploration but can also drive complex dashboards or even bokeh apps.
18
+
19
+ ## Declaring dynamic data
20
+
21
+ We will begin by declaring a function which loads some data. In this case we will just load some stock data from the bokeh but you could imagine querying this data using REST interface or some other API or even loading some large collection of data from disk or generating the data from some simulation or data processing job.
22
+
23
+
24
+ ```python
25
+ def load_symbol(symbol, **kwargs):
26
+ df = pd.DataFrame(getattr(stocks, symbol))
27
+ df['date'] = df.date.astype('datetime64[ns]')
28
+ return hv.Curve(df, ('date', 'Date'), ('adj_close', 'Adjusted Close'))
29
+
30
+ stock_symbols = ['AAPL', 'FB', 'GOOG', 'IBM', 'MSFT']
31
+ dmap = hv.DynamicMap(load_symbol, kdims='Symbol').redim.values(Symbol=stock_symbols)
32
+ ```
33
+
34
+ We begin by displaying our DynamicMap to see what we are dealing with. Recall that a ``DynamicMap`` is only evaluated when you request the key so the ``load_symbol`` function is only executed when first displaying the ``DynamicMap`` and whenever we change the widget dropdown:
35
+
36
+
37
+ ```python
38
+ dmap
39
+ ```
40
+
41
+ ## Processing data
42
+
43
+ It is very common to want to process some data, for this purpose HoloViews provides so-called ``Operations``, which are described in detail in the [Transforming Elements](./11-Transforming_Elements.ipynb). ``Operations`` are simply parameterized functions, which take HoloViews objects as input, transform them in some way and then return the output.
44
+
45
+ In combination with [Dimensioned Containers](./05-Dimensioned_Containers.ipynb) such as ``HoloMap`` and ``GridSpace`` they are a powerful way to explore how the parameters of your transform affect the data. We will start with a simple example. HoloViews provides a ``rolling`` function which smoothes timeseries data with a rolling window. We will apply this operation with a ``rolling_window`` of 30, i.e. roughly a month of our daily timeseries data:
46
+
47
+
48
+ ```python
49
+ smoothed = rolling(dmap, rolling_window=30)
50
+ smoothed
51
+ ```
52
+
53
+ As you can see the ``rolling`` operation applies directly to our ``DynamicMap``, smoothing each ``Curve`` before it is displayed. Applying an operation to a ``DynamicMap`` keeps the data as a ``DynamicMap``, this means the operation is also applied lazily whenever we display or select a different symbol in the dropdown widget.
54
+
55
+ ### Dynamically evaluating parameters on operations and transforms with ``.apply``
56
+
57
+ The ``.apply`` method allows us to automatically build a dynamic pipeline given an object and some operation or function along with parameter, stream or widget instances passed in as keyword arguments. Internally it will then build a `Stream` to ensure that whenever one of these changes the plot is updated. To learn more about streams see the [Responding to Events](./12-Responding_to_Events.ipynb).
58
+
59
+ This mechanism allows us to build powerful pipelines by linking parameters on a user defined class or even an external widget, e.g. here we import an ``IntSlider`` widget from [``panel``](https://pyviz.panel.org):
60
+
61
+
62
+ ```python
63
+ import panel as pn
64
+
65
+ slider = pn.widgets.IntSlider(name='rolling_window', start=1, end=100, value=50)
66
+ ```
67
+
68
+ Using the ``.apply`` method we could now apply the ``rolling`` operation to the DynamicMap and link the slider to the operation's ``rolling_window`` parameter (which also works for simple functions as will be shown below). However, to further demonstrate the features of `dim` expressions and the `.transform` method, which we first introduced in the [Transforming elements user guide](11-Transforming_Elements.ipynb), we will instead apply the rolling mean using the `.df` namespace accessor on a `dim` expression:
69
+
70
+
71
+ ```python
72
+ rolled_dmap = dmap.apply.transform(adj_close=hv.dim('adj_close').df.rolling(slider).mean())
73
+
74
+ rolled_dmap
75
+ ```
76
+
77
+ The ``rolled_dmap`` is another DynamicMap that defines a simple two-step pipeline, which calls the original callback when the ``symbol`` changes and reapplies the expression whenever the slider value changes. Since the widget's value is now linked to the plot via a ``Stream`` we can display the widget and watch the plot update:
78
+
79
+
80
+ ```python
81
+ slider
82
+ ```
83
+
84
+ The power of building pipelines is that different visual components can share the same inputs but compute very different things from that data. The part of the pipeline that is shared is only evaluated once making it easy to build efficient data processing code. To illustrate this we will also apply the ``rolling_outlier_std`` operation which computes outliers within the ``rolling_window`` and again we will supply the widget ``value``:
85
+
86
+
87
+ ```python
88
+ outliers = dmap.apply(rolling_outlier_std, rolling_window=slider.param.value)
89
+
90
+ rolled_dmap * outliers.opts(color='red', marker='triangle')
91
+ ```
92
+
93
+ We can chain operations like this indefinitely and attach parameters or explicit streams to each stage. By chaining we can watch our visualization update whenever we change a stream value anywhere in the pipeline and HoloViews will be smart about which parts of the pipeline are recomputed, which allows us to build complex visualizations very quickly.
94
+
95
+ The ``.apply`` method is also not limited to operations. We can just as easily apply a simple Python function to each object in the ``DynamicMap``. Here we define a function to compute the residual between the original ``dmap`` and the ``rolled_dmap``.
96
+
97
+
98
+ ```python
99
+ def residual_fn(overlay):
100
+ # Get first and second Element in overlay
101
+ el1, el2 = overlay.get(0), overlay.get(1)
102
+
103
+ # Get x-values and y-values of curves
104
+ xvals = el1.dimension_values(0)
105
+ yvals = el1.dimension_values(1)
106
+ yvals2 = el2.dimension_values(1)
107
+
108
+ # Return new Element with subtracted y-values
109
+ # and new label
110
+ return el1.clone((xvals, yvals-yvals2),
111
+ vdims='Residual')
112
+ ```
113
+
114
+ If we overlay the two DynamicMaps we can then dynamically broadcast this function to each of the overlays, producing a new DynamicMap which responds to both the symbol selector widget and the slider:
115
+
116
+
117
+ ```python
118
+ residual = (dmap * rolled_dmap).apply(residual_fn)
119
+
120
+ residual
121
+ ```
122
+
123
+ In later guides we will see how we can combine HoloViews plots and Panel widgets into custom layouts allowing us to define complex dashboards. For more information on how to deploy bokeh apps from HoloViews and build dashboards see the [Deploying Bokeh Apps](./Deploying_Bokeh_Apps.ipynb) and [Dashboards](./17-Dashboards.ipynb) guides. To get a quick idea of what this might look like let's compose all the components we have no built:
hvplot_docs/15-Large_Data.md ADDED
@@ -0,0 +1,576 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Working with large data using Datashader
2
+
3
+ The various plotting-library backends supported by HoloViews, such as Matplotlib, Bokeh, and Plotly, each have limitations on the amount of data that is practical to work with. Bokeh and Plotly in particular mirror your data directly into an HTML page viewable in your browser, which can cause problems when data sizes approach the limited memory available for each web page in current browsers.
4
+
5
+ Luckily, a visualization of even the largest dataset will be constrained by the resolution of your display device, and so one approach to handling such data is to pre-render or rasterize the data into a fixed-size array or image *before* sending it to the backend plotting library and thus to your local web browser. The [Datashader](https://github.com/bokeh/datashader) library provides a high-performance big-data server-side rasterization pipeline that works seamlessly with HoloViews to support datasets that are orders of magnitude larger than those supported natively by the plotting-library backends, including millions or billions of points even on ordinary laptops.
6
+
7
+ Here, we will see how and when to use Datashader with HoloViews Elements and Containers. For simplicity in this discussion we'll focus on simple synthetic datasets, but [Datashader's examples](http://datashader.org/topics) include a wide variety of real datasets that give a much better idea of the power of using Datashader with HoloViews, and [HoloViz.org](http://holoviz.org) shows how to install and work with HoloViews and Datashader together.
8
+
9
+ <style>.container { width:100% !important; }</style>
10
+
11
+
12
+ ```python
13
+ import datashader as ds
14
+ import numpy as np
15
+ import holoviews as hv
16
+ import pandas as pd
17
+ import numpy as np
18
+
19
+ from holoviews import opts
20
+ from holoviews.operation.datashader import datashade, rasterize, shade, dynspread, spread
21
+ from holoviews.operation.resample import ResampleOperation2D
22
+ from holoviews.operation import decimate
23
+
24
+ hv.extension('bokeh','matplotlib', width=100)
25
+
26
+ # Default values suitable for this notebook
27
+ decimate.max_samples=1000
28
+ dynspread.max_px=20
29
+ dynspread.threshold=0.5
30
+ ResampleOperation2D.width=500
31
+ ResampleOperation2D.height=500
32
+
33
+ def random_walk(n, f=5000):
34
+ """Random walk in a 2D space, smoothed with a filter of length f"""
35
+ xs = np.convolve(np.random.normal(0, 0.1, size=n), np.ones(f)/f).cumsum()
36
+ ys = np.convolve(np.random.normal(0, 0.1, size=n), np.ones(f)/f).cumsum()
37
+ xs += 0.1*np.sin(0.1*np.array(range(n-1+f))) # add wobble on x axis
38
+ xs += np.random.normal(0, 0.005, size=n-1+f) # add measurement noise
39
+ ys += np.random.normal(0, 0.005, size=n-1+f)
40
+ return np.column_stack([xs, ys])
41
+
42
+ def random_cov():
43
+ """Random covariance for use in generating 2D Gaussian distributions"""
44
+ A = np.random.randn(2,2)
45
+ return np.dot(A, A.T)
46
+
47
+ def time_series(T = 1, N = 100, mu = 0.1, sigma = 0.1, S0 = 20):
48
+ """Parameterized noisy time series"""
49
+ dt = float(T)/N
50
+ t = np.linspace(0, T, N)
51
+ W = np.random.standard_normal(size = N)
52
+ W = np.cumsum(W)*np.sqrt(dt) # standard brownian motion
53
+ X = (mu-0.5*sigma**2)*t + sigma*W
54
+ S = S0*np.exp(X) # geometric brownian motion
55
+ return S
56
+ ```
57
+
58
+ <center><div class="alert alert-info" role="alert">This notebook makes use of dynamic updates, which require a running a live Jupyter or Bokeh server.<br>
59
+ When viewed statically, the plots will not update fully when you zoom and pan.<br></div></center>
60
+
61
+ # Principles of datashading
62
+
63
+ Because HoloViews elements are fundamentally data containers, not visualizations, you can very quickly declare elements such as ``Points`` or ``Path`` containing datasets that may be as large as the full memory available on your machine (or even larger if using Dask dataframes). So even for very large datasets, you can easily specify a data structure that you can work with for making selections, sampling, aggregations, and so on. However, as soon as you try to visualize it directly with either the Matplotlib, Plotly, or Bokeh plotting extensions, the rendering process may be prohibitively expensive.
64
+
65
+ Let's start with a simple example that's easy to visualize in any plotting library:
66
+
67
+
68
+ ```python
69
+ np.random.seed(1)
70
+ points = hv.Points(np.random.multivariate_normal((0,0), [[0.1, 0.1], [0.1, 1.0]], (1000,)),label="Points")
71
+ paths = hv.Path([random_walk(2000,30)], kdims=["u","v"], label="Paths")
72
+
73
+ points + paths
74
+ ```
75
+
76
+ These browser-based plots are fully interactive, as you can see if you select the Wheel Zoom or Box Zoom tools and use your scroll wheel or click and drag.
77
+
78
+ Because all of the data in these plots gets transferred directly into the web browser, the interactive functionality will be available even on a static export of this figure as a web page. Note that even though the visualization above is not computationally expensive, even with just 1000 points as in the scatterplot above, the plot already suffers from [overplotting](https://anaconda.org/jbednar/plotting_pitfalls), with later points obscuring previously plotted points.
79
+
80
+ With much larger datasets, these issues will quickly make it impossible to see the true structure of the data. We can easily declare 50X or 1000X larger versions of the same plots above, but if we tried to visualize them directly they would be unusably slow even if the browser did not crash:
81
+
82
+
83
+ ```python
84
+ np.random.seed(1)
85
+ points = hv.Points(np.random.multivariate_normal((0,0), [[0.1, 0.1], [0.1, 1.0]], (1000000,)),label="Points")
86
+ paths = hv.Path([0.15*random_walk(100000) for i in range(10)], kdims=["u","v"], label="Paths")
87
+
88
+ #points + paths ## Danger! Browsers can't handle 1 million points!
89
+ ```
90
+
91
+ Luckily, HoloViews Elements are just containers for data and associated metadata, not plots, so HoloViews can generate entirely different types of visualizations from the same data structure when appropriate. For instance, in the plot on the left below you can see the result of applying a `decimate()` operation acting on the `points` object, which will automatically downsample this million-point dataset to at most 1000 points at any time as you zoom in or out:
92
+
93
+
94
+ ```python
95
+ decimate( points).relabel("Decimated Points") + \
96
+ rasterize(points).relabel("Rasterized Points").opts(colorbar=True, width=350) + \
97
+ rasterize(paths ).relabel("Rasterized Paths")
98
+ ```
99
+
100
+ Decimating a plot in this way can be useful, but it discards most of the data even while still suffering from overplotting.
101
+
102
+ If you have Datashader installed, you can instead use Datashader operations like `rasterize()` to create a dynamic Datashader-based Bokeh plot. The middle plot above shows the result of using `rasterize()` to create a dynamic Datashader-based plot out of an Element with arbitrarily large data. In the rasterized version, the data is binned into a fixed-size 2D array automatically on every zoom or pan event, revealing all the data available at that zoom level and avoiding issues with overplotting by dynamically rescaling the colors used. Each pixel is colored by how many datapoints fall in that pixel, faithfully revealing the data's distribution in a easy-to-display plot. The colorbar indicates the number of points indicated by that color, up to 300 or so for the pixels with the most points here. The same process is used for the line-based data in the Paths plot, where darker colors represent path intersections.
103
+
104
+ These two Datashader-based plots are similar to the native Bokeh plots above, but instead of making a static Bokeh plot that embeds points or line segments directly into the browser, HoloViews sets up a Bokeh plot with dynamic callbacks instructing Datashader to rasterize the data into a fixed-size array (effectively a 2D histogram) instead. The dynamic re-rendering provides an interactive user experience, even though the data itself is never provided directly to the browser. Of course, because the full data is not in the browser, a static export of this page (e.g. on holoviews.org or on anaconda.org) will only show the initially rendered version, and will not update with new rasterized arrays when zooming as it will when there is a live Python process available.
105
+
106
+ Though you can no longer have a completely interactive exported file, with the Datashader version on a live server you can now change the number of data points from 1000000 to 10000000 or more to see how well your machine will handle larger datasets. It will get a bit slower, but if you have enough memory, it should still be very usable, and should never crash your browser like transferring the whole dataset into your browser would. If you don't have enough memory, you can instead set up a [Dask](http://dask.pydata.org) dataframe as shown in other Datashader examples, which will provide out-of-core and/or distributed processing to handle even the largest datasets if you have enough computational power and memory or are willing to wait for out-of-core computation.
107
+
108
+ # HoloViews operations for datashading
109
+
110
+ HoloViews provides several operations for calling Datashader on HoloViews elements, including `rasterize()`, `shade()`, and `datashade()`.
111
+
112
+ `rasterize()` uses Datashader to render the data into what is by default a 2D histogram, where every array cell counts the data points falling into that pixel. Bokeh then colormaps that array, turning each cell into a pixel in an image.
113
+
114
+ Instead of having Bokeh do the colormapping, you can instruct Datashader to do so, by wrapping the output of `rasterize()` in a call to `shade()`, where `shade()` is Datashader's colormapping function. The `datashade()` operation is also provided as a simple macro, where `datashade(x)` is equivalent to `shade(rasterize(x))`:
115
+
116
+
117
+ ```python
118
+ ropts = dict(colorbar=True, tools=["hover"], width=350)
119
+
120
+ rasterize( points).opts(cmap="kbc_r", cnorm="linear").relabel('rasterize()').opts(**ropts).hist() + \
121
+ shade(rasterize(points), cmap="kbc_r", cnorm="linear").relabel("shade(rasterize())") + \
122
+ datashade( points, cmap="kbc_r", cnorm="linear").relabel("datashade()")
123
+ ```
124
+
125
+ In all three of the above plots, `rasterize()` is being called to aggregate the data (a large set of x,y locations) into a rectangular grid, with each grid cell counting up the number of points that fall into it. In the first plot, only `rasterize()` is done, and the resulting numeric array of counts is passed to Bokeh for colormapping. That way hover and colorbars can be supported (as shown), and Bokeh can then provide dynamic (client-side, browser-based) colormapping tools in JavaScript, allowing users to have dynamic control over even static HTML plots. For instance, in this case, users can use the Box Select tool and select a range of the histogram shown, dynamically remapping the colors used in the plot to cover the selected range.
126
+
127
+ The other two plots should be identical in appearance, but with the numerical array output of `rasterize()` mapped into RGB colors by Datashader itself, in Python ("server-side"), which allows some special Datashader computations described below but prevents other Bokeh-based features like hover and colorbars from being used. Here we've instructed Datashader to use the same colormap used by bokeh, so that the plots look similar, but as you can see the `rasterize()` colormap is determined by a HoloViews plot option, while the `shade` and `datashade` colormap is determined by an argument to those operations. See ``hv.help(rasterize)``, ``hv.help(shade)``, and ``hv.help(datashade)`` for options that can be selected, and the [Datashader web site](http://datashader.org) for all the details. HoloViews also provides lower-level `aggregate()` and `regrid()` operations that implement `rasterize()` and give more control over how the data is aggregated, but these are not needed for typical usage.
128
+
129
+ # Setting options
130
+
131
+ By their nature, the datashading operations accept one HoloViews Element type and return a different Element type. Regardless of what type they are given, `rasterize()` returns an `hv.Image`, while `shade()` and `datashade()` return an `hv.RGB`. It is important to keep this transformation in mind, because HoloViews options that you set on your original Element type are not normally transferred to your new Element:
132
+
133
+
134
+ ```python
135
+ points2 = decimate(points, dynamic=False, max_samples=3000)
136
+ points2.opts(color="green", size=6, marker="s")
137
+
138
+ points2 + rasterize(points2).relabel("Rasterized") + datashade(points2).relabel("Datashaded")
139
+ ```
140
+
141
+ The datashaded plot represents each point as a single pixel, many of which are very difficult to see, and you can see that the color, size, and marker shape that you set on the Points element will not be applied to the rasterized or datashaded plot, because `size` and `marker` are not directly applicable to the numerical arrays of `hv.Image` and the pixel arrays of `hv.RGB`.
142
+
143
+ If you want to use Datashader to recreate the options from the original plot, you can usually do so, but you will have to use the various Datashader-specific features explained in the sections below along with HoloViews options specifically for `hv.Image` or `hv.RGB`. For example:
144
+
145
+
146
+ ```python
147
+ w=225
148
+
149
+ points2 + \
150
+ spread(rasterize(points2, width=w, height=w), px=4, shape='square').opts(cmap=["green"]).relabel("Rasterized") + \
151
+ spread(datashade(points2, width=w, height=w, cmap=["green"]), px=4, shape='square').relabel("Datashaded")
152
+ ```
153
+
154
+ Note that by forcing the single-color colormap `["green"]`, Datashader's support for avoiding overplotting has been lost. In most cases you will want to reveal the underlying distribution while avoiding overplotting, either by using a proper colormap (**Rasterized** below) or by using the alpha channel to convey the number of overlapping points (**Datashaded** below).
155
+
156
+
157
+ ```python
158
+ import bokeh.palettes as bp
159
+ greens = bp.Greens[256][::-1][64:]
160
+ ```
161
+
162
+
163
+ ```python
164
+ points2 + \
165
+ spread(rasterize(points2, width=w, height=w), px=4, shape='square').opts(cmap=greens, cnorm='eq_hist').relabel("Rasterized") +\
166
+ spread(datashade(points2, width=w, height=w, cmap="green", cnorm='eq_hist'), px=4, shape='square').relabel("Datashaded")
167
+ ```
168
+
169
+ # Colormapping
170
+
171
+ As you can see above, the choice of colormap and the various colormapping options can be very important for datashaded plots. One issue often seen in large, real-world datasets is that there is structure at many spatial and value scales, which requires special attention to colormapping options. This example dataset from the [Datashader documentation](https://datashader.org/getting_started/Pipeline.html) illustrates the issues, with data clustering at five different spatial scales:
172
+
173
+
174
+ ```python
175
+ num=10000
176
+ np.random.seed(1)
177
+
178
+ dists = {cat: pd.DataFrame(dict([('x',np.random.normal(x,s,num)),
179
+ ('y',np.random.normal(y,s,num)),
180
+ ('val',val),
181
+ ('cat',cat)]))
182
+ for x, y, s, val, cat in
183
+ [( 2, 2, 0.03, 10, "d1"),
184
+ ( 2, -2, 0.10, 20, "d2"),
185
+ ( -2, -2, 0.50, 30, "d3"),
186
+ ( -2, 2, 1.00, 40, "d4"),
187
+ ( 0, 0, 3.00, 50, "d5")] }
188
+
189
+ df = pd.concat(dists,ignore_index=True)
190
+ df["cat"]=df["cat"].astype("category")
191
+ df
192
+ ```
193
+
194
+ Each of the five categories has 10000 points, but distributed over different spatial areas. Bokeh supports three colormap normalization options, which each behave differently:
195
+
196
+
197
+ ```python
198
+ ropts = dict(tools=["hover"], height=380, width=330, colorbar=True, colorbar_position="bottom")
199
+
200
+ hv.Layout([rasterize(hv.Points(df)).opts(**ropts).opts(cnorm=n).relabel(n)
201
+ for n in ["linear", "log", "eq_hist"]])
202
+ ```
203
+
204
+ Here, the `linear` map is easy to interpret, but nearly all of the pixels are drawn in the lightest blue, because the highest-count pixel (around a count of 6000) is much larger in value than the typical pixels. The other two plots show the full structure (five concentrations of data points, including one in the background), with `log` using a standard logarithmic transformation of the count data before colormapping, and `eq_hist` using a histogram-equalization technique (see the [Datashader docs](https://datashader.org/getting_started/Pipeline.html)) to reveal structure without any assumptions about the incoming distribution (but with an irregularly spaced colormap that makes the numeric values difficult to reason about). In practice, it is generally a good idea to use `eq_hist` when exploring a large dataset initially, so that you will see any structure present, then switch to `log` or `linear` as appropriate to share the plots with a simpler-to-explain colormap. All three of these options are supported by the various backends (including Bokeh version 2.2.3 or later) and by `shade()` and `datashade()` except that `eq_hist` is not yet available for the Plotly backend.
205
+
206
+ Since datashader only sends the data currently in view to the plotting backend, the default behavior is to rescale the colormap to the range of the visible data as the zoom level changes. This behavior may not be desirable when working with images; to instead use a fixed colormap range, the `clim` parameter can be passed to the `bokeh` backend via the `opts()` method. Note that this approach works with `rasterize()` where the colormapping is done by the `bokeh` backend. With `datashade()`, the colormapping is done with the `shade()` function which takes a `clims` parameter directly instead of passing additional parameters to the backend via `opts()`. For example (removing the semicolon in a live notebook to see the output):
207
+
208
+
209
+ ```python
210
+ pts1 = rasterize(hv.Points(df)).opts(**ropts).opts(tools=[], cnorm='log', axiswise=True)
211
+ pts2 = rasterize(hv.Points(df)).opts(**ropts).opts(tools=[], cnorm='log', axiswise=True)
212
+
213
+ pts1 + pts2.opts(clim=(0, 10000));
214
+ ```
215
+
216
+ <img src="http://assets.holoviews.org/gifs/guides/user_guide/Large_Data/rasterize_clim_example.gif"></img>
217
+
218
+ By default, pixels with an integer count of zero or a floating-point value of NaN are transparent, letting the plot background show through so that the data can be used in overlays. If you want zero to map to the lowest colormap color instead to make a dense, fully filled-in image, you can use `redim.nodata` to set the `Dimension.nodata` parameter to None:
219
+
220
+
221
+ ```python
222
+ hv.Layout([rasterize(hv.Points(df), vdim_prefix='').redim.nodata(Count=n)\
223
+ .opts(**ropts, cnorm="eq_hist").relabel("nodata="+str(n))
224
+ for n in [0, None]])
225
+ ```
226
+
227
+ ## Spreading and antialiasing
228
+
229
+ By default, Datashader treats points and lines as infinitesimal in width, such that a given point or small bit of line segment appears in at most one pixel. This approach ensures that the overall distribution of the points will be mathematically well founded -- each pixel will scale in value directly by the number of points that fall into it, or by the lines that cross it. As a consequence, Datashader's "marker size" and "line width" are effectively one pixel by default.
230
+
231
+ However, many monitors are sufficiently high resolution that a single-pixel point or line can be difficult to see---one pixel may not be visible at all on its own, and even if it is visible it is often difficult to see its color. To compensate for this, HoloViews provides access to Datashader's raster-based "spreading" (a generalization of image dilation and convolution), which makes isolated nonzero cells "spread" into adjacent ones for visibility. There are two varieties of spreading supported:
232
+
233
+ 1. ``spread``: fixed spreading of a certain number of cells (pixels), which is useful if you want to be sure how much spreading is done regardless of the properties of the data.
234
+ 2. ``dynspread``: spreads up to a maximum size as long as it does not exceed a specified fraction of adjacency between cells (pixels) (controlled by a `threshold` parameter).
235
+
236
+ Dynamic spreading is typically more useful for interactive plotting, because it adjusts depending on how close the datapoints are to each other on screen. As of Datashader 0.12, both types of spreading are supported for both `rasterize()` and `shade()`, but previous Datashader versions only support spreading on the RGB output of `shade()`.
237
+
238
+ As long as you have Datashader 0.12 or later, you can compare the results when you zoom the two plots below; when you zoom in far enough you should be able to see that the in the two zoomed-in plots below, then zoom out to see that the plots are the same when points are clustered together to form a distribution. (If running a live notebook; remove the semicolon so that you see the live output rather than the saved GIF.)
239
+
240
+
241
+ ```python
242
+ pts = rasterize(points).opts(cnorm='eq_hist')
243
+
244
+ pts + dynspread(pts);
245
+ ```
246
+
247
+ <img src="http://assets.holoviews.org/gifs/guides/user_guide/Large_Data/dynspread.gif"></img>
248
+
249
+ Both plots show the same data, and look identical when zoomed out, but when zoomed in enough you should be able to see the individual data points on the right while the ones on the left are barely visible. The dynspread parameters typically need some hand tuning, as the only purpose of such spreading is to make things visible on a particular monitor for a particular observer; the underlying mathematical operations in Datashader do not normally need parameters to be adjusted.
250
+
251
+ Dynspread is not usable with connected plots like trajectories or curves, because the spreading amount is measured by the fraction of cells that have neighbors closer than the given spread distance, which is always 100% when datapoints are connected together. For connected plots you can instead use `spread` with a fixed value to expand patterns by `px` in every direction after they are drawn, or (for Datashader 0.14 or later) pass an explicit width like `line_width=1` to the rasterizer (at some cost in performance) to draw fully antialiased lines with the specified width:
252
+
253
+
254
+ ```python
255
+ rasterize(paths).relabel("Rasterized") + \
256
+ spread(rasterize(paths), px=1).relabel("Spread 1") + \
257
+ rasterize(paths, line_width=2).relabel("Antialiased line_width 2")
258
+ ```
259
+
260
+ # Multidimensional plots
261
+
262
+ The above plots show two dimensions of data plotted along *x* and *y*, but Datashader operations can be used with additional dimensions as well. For instance, an extra dimension (here called `k`), can be treated as a category label and used to colorize the points or lines, aggregating the data points separately depending on which category value they have. Compared to a standard overlaid scatterplot that would suffer from overplotting, here the result will be merged mathematically by Datashader, completely avoiding any overplotting issues except any local issues that may arise from spreading when zoomed in:
263
+
264
+
265
+ ```python
266
+ np.random.seed(3)
267
+ kdims=['d1','d2']
268
+ num_ks=8
269
+
270
+ def rand_gauss2d(value=0, n=100000):
271
+ """Return a randomly shaped 2D Gaussian distribution with an associated numeric value"""
272
+ g = 100*np.random.multivariate_normal(np.random.randn(2), random_cov(), (n,))
273
+ return np.hstack((g,value*np.ones((g.shape[0],1))))
274
+ ```
275
+
276
+
277
+ ```python
278
+ gaussians = {str(i): hv.Points(rand_gauss2d(i), kdims, "i") for i in range(num_ks)}
279
+
280
+ c = dynspread(datashade(hv.NdOverlay(gaussians, kdims='k'), aggregator=ds.by('k', ds.count())))
281
+ m = dynspread(datashade(hv.NdOverlay(gaussians, kdims='k'), aggregator=ds.by('k', ds.mean("i"))))
282
+
283
+ c.opts(width=400) + m.opts(width=400)
284
+ ```
285
+
286
+ Above you can see that (as of Datashader 0.11) categorical aggregates can take any reduction function, either `count`ing the datapoints (left) or reporting some other statistic (e.g. the mean value of a column, right). This type of categorical mixing is currently only supported by `shade()` and `datashade()`, not `rasterize()` alone, because it depends on Datashader's custom color mixing code.
287
+
288
+ Categorical aggregates are one way to allow separate lines or other shapes to be visually distinctive from one another while avoiding obscuring data due to overplotting:
289
+
290
+
291
+ ```python
292
+ lines = {str(i): hv.Curve(time_series(N=10000, S0=200+np.random.rand())) for i in range(num_ks)}
293
+ lineoverlay = hv.NdOverlay(lines, kdims='k')
294
+ datashade(lineoverlay, pixel_ratio=2, line_width=4, aggregator=ds.by('k', ds.count())).opts(width=800)
295
+ ```
296
+
297
+ As you can see, overlapping colors yield color mixtures that indicate that the given pixels contain data from multiple curves, which helps users realize where they need to zoom in to see further detail.
298
+
299
+ Note that Bokeh only ever sees an image come out of `datashade`, not any of the actual data. As a result, providing legends and keys has to be done separately, though we are hoping to make this process more seamless. For now, you can show a legend by adding a suitable collection of "fake" labeled points (size zero and thus invisible):
300
+
301
+
302
+ ```python
303
+ # definition copied here to ensure independent pan/zoom state for each dynamic plot
304
+ gaussspread2 = dynspread(datashade(hv.NdOverlay(gaussians, kdims=['k']), aggregator=ds.by('k', ds.count())))
305
+
306
+ from datashader.colors import Sets1to3 # default datashade() and shade() color cycle
307
+ color_key = list(enumerate(Sets1to3[0:num_ks]))
308
+ color_points = hv.NdOverlay({k: hv.Points([(0,0)], label=str(k)).opts(color=v, size=0) for k, v in color_key})
309
+
310
+ (color_points * gaussspread2).opts(width=600)
311
+ ```
312
+
313
+ Here the dummy points are at (0,0) for this dataset, but would need to be at another suitable value for data that is in a different range.
314
+
315
+ ## Working with time series
316
+
317
+ HoloViews also makes it possible to datashade large timeseries using the ``datashade`` and ``rasterize`` operations. For smoother lines, datashader implements anti-aliasing if a `line_width` > 0 is set:
318
+
319
+
320
+ ```python
321
+ dates = pd.date_range(start="2014-01-01", end="2016-01-01", freq='1D') # or '1min'
322
+ curve = hv.Curve((dates, time_series(N=len(dates), sigma = 1)))
323
+ rasterize(curve, width=800, line_width=3, pixel_ratio=2).opts(width=800, cmap=['lightblue','blue'])
324
+ ```
325
+
326
+ Here we're also doubling the resolution in x and y using `pixel_ratio=2`, allowing for more precise rendering of the line shape; higher pixel ratios work well for lines and other shapes, though they can make individual points more difficult to see in points plots.
327
+
328
+ HoloViews also supplies some operations that are useful in combination with Datashader timeseries. For instance, you can compute a rolling mean of the results and then show a subset of outlier points, which will then support hover, selection, and other interactive Bokeh features:
329
+
330
+
331
+ ```python
332
+ from holoviews.operation.timeseries import rolling, rolling_outlier_std
333
+ smoothed = rolling(curve, rolling_window=50)
334
+ outliers = rolling_outlier_std(curve, rolling_window=50, sigma=2)
335
+
336
+ ds_curve = rasterize(curve, line_width=2.5, pixel_ratio=2).opts(cmap=["lightblue","blue"])
337
+ curvespread = rasterize(smoothed, line_width=6, pixel_ratio=2).opts(cmap=["pink","red"], width=800)
338
+
339
+ (ds_curve * curvespread * outliers).opts(
340
+ opts.Scatter(line_color="black", fill_color="red", size=10, tools=['hover', 'box_select'], width=800))
341
+ ```
342
+
343
+ Another option when working with time series is to downsample the data before plotting it. This can be done with `downsample1D`. Algorithms supported are `lttb` (Largest Triangle Three Buckets) and `nth` element. The two algorithm is overlaid on top of the original curve in the example below.
344
+
345
+
346
+ ```python
347
+ from holoviews.operation.downsample import downsample1d
348
+
349
+ lttb = downsample1d(curve)
350
+ nth = downsample1d(curve, algorithm="nth")
351
+
352
+ lttb_com = (curve * lttb).opts(width=800, title="lttb comparison")
353
+ nth_com = (curve * nth).opts(width=800, title="nth comparison")
354
+
355
+ (lttb_com + nth_com).cols(1)
356
+ ```
357
+
358
+ The result of all these operations can be laid out, overlaid, selected, and sampled just like any other HoloViews element, letting you work naturally with even very large datasets.
359
+
360
+ Note that the above plot will look blocky in a static export (such as on anaconda.org), because the exported version is generated without taking the size of the actual plot (using default height and width for Datashader) into account, whereas the live notebook automatically regenerates the plot to match the visible area on the page.
361
+
362
+ # Element types supported for Datashading
363
+
364
+ Fundamentally, what Datashader does is to rasterize data, i.e., render a representation of it into a regularly gridded rectangular portion of a two-dimensional plane. Datashader natively supports six basic types of rasterization:
365
+
366
+ - **points**: zero-dimensional objects aggregated by position alone, each point covering zero area in the plane and thus falling into exactly one grid cell of the resulting array (if the point is within the bounds being aggregated).
367
+ - **line**: polyline/multiline objects (connected series of line segments), with each segment having a fixed length but either zero width (not antialiased) or a specified width, and crossing each grid cell at most once.
368
+ - **area**: a region to fill either between the supplied y-values and the origin or between two supplied lines.
369
+ - **trimesh**: irregularly spaced triangular grid, with each triangle covering a portion of the 2D plane and thus potentially crossing multiple grid cells (thus requiring interpolation/upsampling). Depending on the zoom level, a single pixel can also include multiple triangles, which then becomes similar to the `points` case (requiring aggregation/downsampling of all triangles covered by the pixel).
370
+ - **raster**: an axis-aligned regularly gridded two-dimensional subregion of the plane, with each grid cell in the source data covering more than one grid cell in the output grid (requiring interpolation/upsampling), or with each grid cell in the output grid including contributions from more than one grid cell in the input grid (requiring aggregation/downsampling).
371
+ - **quadmesh**: a recti-linear or curvi-linear mesh (like a raster, but allowing nonuniform spacing and coordinate mapping) where each quad can cover one or more cells in the output (requiring upsampling, currently only as nearest neighbor), or with each output grid cell including contributions from more than one input grid cell (requiring aggregation/downsampling).
372
+ - **polygons**: arbitrary filled shapes in 2D space (bounded by a piecewise linear set of segments), optionally punctuated by similarly bounded internal holes.
373
+
374
+ Datashader focuses on implementing those four cases very efficiently, and HoloViews in turn can use them to render a very large range of specific types of data:
375
+
376
+ ### Supported Elements
377
+
378
+ - **points**: [`hv.Nodes`](../reference/elements/bokeh/Graph.ipynb), [`hv.Points`](../reference/elements/bokeh/Points.ipynb), [`hv.Scatter`](../reference/elements/bokeh/Scatter.ipynb)
379
+ - **line**: [`hv.Contours`](../reference/elements/bokeh/Contours.ipynb), [`hv.Curve`](../reference/elements/bokeh/Curve.ipynb), [`hv.Path`](../reference/elements/bokeh/Path.ipynb), [`hv.Graph`](../reference/elements/bokeh/Graph.ipynb), [`hv.EdgePaths`](../reference/elements/bokeh/Graph.ipynb), [`hv.Spikes`](../reference/elements/bokeh/Spikes.ipynb), [`hv.Segments`](../reference/elements/bokeh/Segments.ipynb)
380
+ - **area**: [`hv.Area`](../reference/elements/bokeh/Area.ipynb), [`hv.Rectangles`](../reference/elements/bokeh/Rectangles.ipynb), [`hv.Spread`](../reference/elements/bokeh/Spread.ipynb)
381
+ - **raster**: [`hv.Image`](../reference/elements/bokeh/Image.ipynb), [`hv.HSV`](../reference/elements/bokeh/HSV.ipynb), [`hv.RGB`](../reference/elements/bokeh/RGB.ipynb)
382
+ - **trimesh**: [`hv.TriMesh`](../reference/elements/bokeh/TriMesh.ipynb)
383
+ - **quadmesh**: [`hv.QuadMesh`](../reference/elements/bokeh/QuadMesh.ipynb)
384
+ - **polygons**: [`hv.Polygons`](../reference/elements/bokeh/Polygons.ipynb)
385
+
386
+ Other HoloViews elements *could* be supported, but do not currently have a useful datashaded representation:
387
+
388
+ ### Elements not yet supported
389
+
390
+ - **line**: [`hv.Spline`](../reference/elements/bokeh/Spline.ipynb), [`hv.VectorField`](../reference/elements/bokeh/VectorField.ipynb)
391
+ - **raster**: [`hv.HeatMap`](../reference/elements/bokeh/HeatMap.ipynb), [`hv.Raster`](../reference/elements/bokeh/Raster.ipynb)
392
+
393
+ There are also other Elements that are not expected to be useful with datashader because they are isolated annotations, are already summaries or aggregations of other data, have graphical representations that are only meaningful at a certain size, or are text based:
394
+
395
+ ### Not useful to support
396
+
397
+ - datashadable annotations: [`hv.Arrow`](../reference/elements/bokeh/Arrow.ipynb), [`hv.Bounds`](../reference/elements/bokeh/Bounds.ipynb), [`hv.Box`](../reference/elements/bokeh/Box.ipynb), [`hv.Ellipse`](../reference/elements/bokeh/Ellipse.ipynb) (actually do work with datashade currently, but not officially supported because they are not vectorized and thus unlikely to have enough items to be worth datashading)
398
+ - other annotations: [`hv.Labels`](../reference/elements/bokeh/Labels.ipynb), [`hv.HLine`](../reference/elements/bokeh/HLine.ipynb), [`hv.VLine`](../reference/elements/bokeh/VLine.ipynb), [`hv.Text`](../reference/elements/bokeh/Text.ipynb)
399
+ - kdes: [`hv.Distribution`](../reference/elements/bokeh/Distribution.ipynb), [`hv.Bivariate`](../reference/elements/bokeh/Bivariate.ipynb) (already aggregated)
400
+ - categorical/symbolic: [`hv.BoxWhisker`](../reference/elements/bokeh/BoxWhisker.ipynb), [`hv.Bars`](../reference/elements/bokeh/Bars.ipynb), [`hv.ErrorBars`](../reference/elements/bokeh/ErrorBars.ipynb)
401
+ - tables: [`hv.Table`](../reference/elements/bokeh/Table.ipynb), [`hv.ItemTable`](../reference/elements/bokeh/ItemTable.ipynb)
402
+
403
+ Let's make some examples of each supported Element type. First, some dummy data:
404
+
405
+
406
+ ```python
407
+ from bokeh.sampledata.unemployment import data as unemployment
408
+ from bokeh.sampledata.us_counties import data as counties
409
+
410
+ np.random.seed(12)
411
+ N=50
412
+ pts = [(10*i/N, np.sin(10*i/N)) for i in range(N)]
413
+
414
+ x = y = np.linspace(0, 5, int(np.sqrt(N)))
415
+ xs,ys = np.meshgrid(x,y)
416
+ z = np.sin(xs)*np.cos(ys)
417
+
418
+ r = 0.5*np.sin(0.1*xs**2+0.05*ys**2)+0.5
419
+ g = 0.5*np.sin(0.02*xs**2+0.2*ys**2)+0.5
420
+ b = 0.5*np.sin(0.02*xs**2+0.02*ys**2)+0.5
421
+
422
+ n=20
423
+ coords = np.linspace(-1.5,1.5,n)
424
+ X,Y = np.meshgrid(coords, coords);
425
+ Qx = np.cos(Y) - np.cos(X)
426
+ Qy = np.sin(Y) + np.sin(X)
427
+ Z = np.sqrt(X**2 + Y**2)
428
+
429
+ rect_colors = {True: 'red', False: 'green'}
430
+ s = np.random.randn(100).cumsum()
431
+ e = s + np.random.randn(100)
432
+ ```
433
+
434
+ Next, some options:
435
+
436
+
437
+ ```python
438
+ hv.output(backend='matplotlib')
439
+
440
+ opts.defaults(opts.Layout(vspace=0.1, hspace=0.1, sublabel_format='', fig_size=48))
441
+ eopts = dict(aspect=1, axiswise=True, xaxis='bare', yaxis='bare', xticks=False, yticks=False)
442
+ opts2 = dict(filled=True, edge_color='z')
443
+ rect_opts = opts.Rectangles(lw=0, color=hv.dim('sign').categorize(rect_colors))
444
+ ds_point_opts = dict(aggregator='any')
445
+ ds_line_opts = dict(aggregator='any', line_width=1)
446
+ ResampleOperation2D.width=115
447
+ ResampleOperation2D.height=115
448
+
449
+ ds_opts = {
450
+ hv.Path: ds_line_opts,
451
+ hv.Graph: ds_line_opts,
452
+ hv.Contours: ds_line_opts,
453
+ hv.EdgePaths: ds_line_opts,
454
+ hv.Curve: ds_line_opts,
455
+ hv.Scatter: ds_point_opts,
456
+ hv.Points: ds_point_opts,
457
+ hv.Segments: ds_point_opts,
458
+ hv.Rectangles: dict(aggregator=ds.count_cat('sign'), color_key=rect_colors)
459
+ }
460
+ ```
461
+
462
+ Now, some Elements that support datashading, in categories depending on whether they work best with `spread(rasterize())`, with plain `rasterize()`, or require `datashade()`:
463
+
464
+
465
+ ```python
466
+ tri = hv.TriMesh.from_vertices(hv.Points(np.random.randn(N,3), vdims='z')).opts(**opts2)
467
+
468
+ rasterizable = [hv.Curve(pts)]
469
+ rasterizable += [hv.operation.contours(hv.Image((x,y,z)), levels=10)]
470
+ rasterizable += [hv.Area(np.random.randn(10000).cumsum())]
471
+ rasterizable += [hv.Spread((np.arange(10000), np.random.randn(10000).cumsum(), np.random.randn(10000)*10))]
472
+ rasterizable += [hv.Spikes(np.random.randn(1000))]
473
+ rasterizable += [hv.Graph(((np.zeros(N//2), np.arange(N//2)),))]
474
+ rasterizable += [hv.QuadMesh((Qx,Qy,Z))]
475
+ rasterizable += [hv.Image((x,y,z))]
476
+ rasterizable += [hv.RGB(np.dstack([r,g,b])), hv.HSV(np.dstack([g,b,r]))]
477
+ rasterizable += [tri, tri.edgepaths]
478
+ rasterizable += [hv.Path(counties[(1, 1)], ['lons', 'lats'])]
479
+
480
+ polys = hv.Polygons([dict(county, unemployment=unemployment[k])
481
+ for k, county in counties.items()
482
+ if county['state'] == 'tx'],
483
+ ['lons', 'lats'], ['unemployment']).opts(color='unemployment')
484
+ try:
485
+ import spatialpandas # Needed for datashader polygon support
486
+ rasterizable += [polys]
487
+ except: pass
488
+
489
+ spreadable = [e(pts) for e in [hv.Scatter]]
490
+ spreadable += [hv.Points(counties[(1, 1)], ['lons', 'lats'])]
491
+ spreadable += [hv.Segments((np.arange(100), s, np.arange(100), e))]
492
+
493
+ shadeable = [hv.Rectangles((np.arange(100)-0.4, s, np.arange(100)+0.4, e, s>e), vdims='sign').opts(rect_opts)]
494
+ ```
495
+
496
+ We can now view these with Datashader via `spread(rasterize())`, `rasterize()`, or `datashade()`:
497
+
498
+
499
+ ```python
500
+ def nop(x,**k): return x
501
+ def spread2(e, **k): return spread(rasterize(e, **k), px=2).opts(cnorm='eq_hist', padding=0.1)
502
+ def plot(e, operation=nop):
503
+ return operation(e.relabel(e.__class__.name), **ds_opts.get(e.__class__, {})).opts(**eopts)
504
+
505
+ hv.Layout(
506
+ [plot(e, rasterize) for e in rasterizable] + \
507
+ [plot(e, spread2) for e in spreadable] + \
508
+ [plot(e, datashade) for e in shadeable]).cols(6)
509
+ ```
510
+
511
+ For comparison, you can see the corresponding non-datashaded plots (as long as you leave N lower than 10000 unless you have a long time to wait!):
512
+
513
+
514
+ ```python
515
+ hv.Layout([plot(e) for e in rasterizable + spreadable + shadeable]).cols(6)
516
+ ```
517
+
518
+ The previous two sets of examples use Matplotlib, but if they were switched to Bokeh and you had a live server, they would support dynamic re-rendering on zoom and pan so that you could explore the full range of data available (e.g. even very large raster images, networks, paths, point clouds, or meshes).
519
+
520
+
521
+ ```python
522
+ hv.output(backend='bokeh') # restore bokeh backend in case cells will run out of order
523
+ ```
524
+
525
+ # Container types supported for datashading
526
+
527
+ In the above examples `datashade()` or `rasterize` was called directly on each Element, but these operations can also be called on Containers, in which case each Element in the Container will be datashaded separately (for all Container types other than a Layout):
528
+
529
+
530
+ ```python
531
+ curves = {'+':hv.Curve(pts), '-':hv.Curve([(x, -1.0*y) for x, y in pts])}
532
+ rasterize(hv.HoloMap(curves,'sign'), line_width=3)
533
+ ```
534
+
535
+
536
+ ```python
537
+ rasterize(hv.NdLayout(curves,'sign'), line_width=3)
538
+ ```
539
+
540
+
541
+ ```python
542
+ containers = [hv.Overlay(list(curves.values())), hv.NdOverlay(curves), hv.GridSpace(hv.NdOverlay(curves))]
543
+ hv.Layout([rasterize(e.relabel(e.__class__.name), line_width=3) for e in containers]).cols(3)
544
+ ```
545
+
546
+ # Optimizing performance
547
+
548
+ Datashader and HoloViews have different design principles that are worth keeping in mind when using them in combination, if you want to ensure good overall performance. By design, Datashader supports only a small number of operations and datatypes, focusing only on what can be implemented very efficiently. HoloViews instead focuses on supporting the typical workflows of Python users, recognizing that the most computationally efficient choice is only going to be faster overall if it also minimizes the time users have to spend getting things working.
549
+
550
+ HoloViews thus helps you get something working quickly, but once it is working and you realize that you need to do this often or that it comes up against the limits of your computing hardware, you can consider whether you can get much better performance by considering the following issues and suggestions.
551
+
552
+ ### Use a Datashader-supported data structure
553
+
554
+ HoloViews helpfully tries to convert whatever data you have provided into what Datashader supports, which is good for optimizing your time to an initial solution, but will not always be the fastest approach computationally. If you ensure that you store your data in a format that Datashader understands already, HoloViews can simply pass it down to Datashader without copying or transforming it:
555
+
556
+ 1. For point, line, and trimesh data, Datashader supports Dask and Pandas dataframes, and so those two data sources will be fastest. Of those two, Dask Dataframes will usually be somewhat faster and also able to make use of distributed computational resources and out-of-core processing.
557
+ 2. For rasters and quadmeshes, Datashader supports xarray objects natively, and so if your data is provided as an xarray already, plotting will be faster.
558
+ 3. For polygons Datashader supports [spatialpandas](https://github.com/holoviz/spatialpandas) DataFrames.
559
+
560
+ See the [Datashader docs](http://datashader.org) for examples of dealing with even quite large datasets (in the billions of points) on commodity hardware, including many HoloViews-based examples.
561
+
562
+ ### Cache initial processing with `precompute=True`
563
+
564
+ In the typical case of having datasets much larger than the plot resolution, HoloViews Datashader-based operations that work on the full dataset (`rasterize`, `aggregate`,`regrid`) are computationally expensive; the others are not (`shade`, `spread`, `dynspread`, etc.)
565
+
566
+ The expensive operations are all of type `ResamplingOperation`, which has a parameter `precompute` (see `hv.help(hv.operation.datashader.rasterize)`, etc.) Precompute can be used to get faster performance in interactive usage by caching the last set of data used in plotting (*after* any transformations needed) and reusing it when it is requested again. This is particularly useful when your data is not in one of the supported data formats already and needs to be converted. `precompute` is False by default, because it requires using memory to store the cached data, but if you have enough memory, you can enable it so that repeated interactions (such as zooming and panning) will be much faster than the first one. In practice, most Datashader-plots don't need to do extensive precomputing, but enabling it for TriMesh and Polygon plots can greatly speed up interactive usage.
567
+
568
+ ### Use GPU support
569
+
570
+ Many elements now also support aggregation directly on a GPU-based datastructure such as a [cuDF DataFrame](https://github.com/rapidsai/cudf) or an Xarray DataArray backed by a [cupy](https://github.com/cupy/cupy) array. These data structures can be passed directly to the appropriate HoloViews elements just as you would use a Pandas or other Xarray object. For instance, a cuDF can be used on elements like `hv.Points` and `hv.Curve`, while a cupy-backed DataArray raster or quadmesh can be passed to `hv.QuadMesh` elements. When used with Datashader, the GPU implementation can result in 10-100x speedups, as well as avoiding having to transfer the data out of the GPU for plotting (sending only the final rendered plot out of the GPU's memory). To see which HoloViews elements are supported, see the [datashader performance guide](https://datashader.org/user_guide/Performance.html). As of the Datashader 0.11 release, all point, line, area, and quadmesh aggregations are supported when using a GPU backed datastructure, including raster objects like `hv.Image` if first converted to `hv.Quadmesh`.
571
+
572
+ ### Project data only once
573
+
574
+ If you are working with geographic data using [GeoViews](http://geoviews.org) that needs to be projected before display and/or before datashading, GeoViews will have to do this every time you update a plot, which can drown out the performance improvement you get by using Datashader. GeoViews allows you to project the entire dataset at once using `gv.operation.project`, and once you do this you should be able to use Datashader at full speed.
575
+
576
+ If you follow these suggestions, the combination of HoloViews and Datashader will allow you to work uniformly with data covering a huge range of sizes. Per session or per plot, you can trade off the ability to export user-manipulable plots against file size and browser compatibility, and allowing you to render even the largest dataset faithfully. HoloViews makes the full power of Datashader available in just a few lines of code, giving you a natural way to work with your data regardless of its size.
hvplot_docs/16-Streaming_Data.md ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Working with Streaming Data
2
+
3
+ "Streaming data" is data that is continuously generated, often by some external source like a remote website, a measuring device, or a simulator. This kind of data is common for financial time series, web server logs, scientific applications, and many other situations. We have seen how to visualize any data output by a callable in the [Live Data](07-Live_Data.ipynb) user guide and we have also seen how to use the HoloViews stream system to push events in the user guide sections [Responding to Events](12-Responding_to_Events.ipynb) and [Custom Interactivity](13-Custom_Interactivity.ipynb).
4
+
5
+ This user guide shows a third way of building an interactive plot, using ``DynamicMap`` and streams. Here, instead of pushing plot metadata (such as zoom ranges, user triggered events such as ``Tap`` and so on) to a ``DynamicMap`` callback, the underlying data in the visualized elements are updated directly using a HoloViews ``Stream``.
6
+
7
+ In particular, we will show how the HoloViews ``Pipe`` and ``Buffer`` streams can be used to work with streaming data sources without having to fetch or generate the data from inside the ``DynamicMap`` callable. Apart from simply setting element data from outside a ``DynamicMap``, we will also explore ways of working with streaming data coordinated by the separate [``streamz``](http://matthewrocklin.com/blog/work/2017/10/16/streaming-dataframes-1) library from Matt Rocklin, which can make building complex streaming pipelines much simpler.
8
+
9
+ As this notebook makes use of the ``streamz`` library, you will need to install it with ``conda install streamz`` or ``pip install streamz``.
10
+
11
+
12
+ ```python
13
+ import time
14
+ import numpy as np
15
+ import pandas as pd
16
+ import holoviews as hv
17
+ import streamz
18
+ import streamz.dataframe
19
+
20
+ from holoviews import opts
21
+ from holoviews.streams import Pipe, Buffer
22
+
23
+ hv.extension('bokeh')
24
+ ```
25
+
26
+ ## ``Pipe``
27
+
28
+ A ``Pipe`` allows data to be pushed into a DynamicMap callback to change a visualization, just like the streams in the [Responding to Events](./12-Responding_to_Events.ipynb) user guide were used to push changes to metadata that controlled the visualization. A ``Pipe`` can be used to push data of any type and make it available to a ``DynamicMap`` callback. Since all ``Element`` types accept ``data`` of various forms we can use ``Pipe`` to push data directly to the constructor of an ``Element`` through a DynamicMap.
29
+
30
+
31
+ We can take advantage of the fact that most Elements can be instantiated without providing any data, so we declare the the ``Pipe`` with an empty list, declare the ``DynamicMap``, providing the pipe as a stream, which will dynamically update a ``VectorField`` :
32
+
33
+
34
+ ```python
35
+ pipe = Pipe(data=[])
36
+ vector_dmap = hv.DynamicMap(hv.VectorField, streams=[pipe])
37
+ vector_dmap.opts(color='Magnitude', xlim=(-1, 1), ylim=(-1, 1))
38
+ ```
39
+
40
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/pipe_vectorfield.gif"></img>
41
+
42
+ Having set up this ``VectorField`` tied to a ``Pipe`` we can start pushing data to it varying the orientation of the VectorField:
43
+
44
+
45
+ ```python
46
+ x,y = np.mgrid[-10:11,-10:11] * 0.1
47
+ sine_rings = np.sin(x**2+y**2)*np.pi+np.pi
48
+ exp_falloff = 1/np.exp((x**2+y**2)/8)
49
+
50
+ for i in np.linspace(0, 1, 25):
51
+ time.sleep(0.1)
52
+ pipe.send((x,y,sine_rings*i, exp_falloff))
53
+ ```
54
+
55
+ This approach of using an element constructor directly does not allow you to use anything other than the default key and value dimensions. One simple workaround for this limitation is to use ``functools.partial`` as demonstrated in the **Controlling the length section** below.
56
+
57
+ Since ``Pipe`` is completely general and the data can be any custom type, it provides a completely general mechanism to stream structured or unstructured data. Due to this generality, ``Pipe`` does not offer some of the more complex features and optimizations available when using the ``Buffer`` stream described in the next section.
58
+
59
+ ## ``Buffer``
60
+
61
+ While ``Pipe`` provides a general solution for piping arbitrary data to ``DynamicMap`` callback, ``Buffer`` on the other hand provides a very powerful means of working with streaming tabular data, defined as pandas dataframes, arrays or dictionaries of columns (as well as StreamingDataFrame, which we will cover later). ``Buffer`` automatically accumulates the last ``N`` rows of the tabular data, where ``N`` is defined by the ``length``.
62
+
63
+ The ability to accumulate data allows performing operations on a recent history of data, while plotting backends (such as bokeh) can optimize plot updates by sending just the latest patch. This optimization works only if the ``data`` object held by the ``Buffer`` is identical to the plotted ``Element`` data, otherwise all the data will be updated as normal.
64
+
65
+ #### A simple example: Brownian motion
66
+
67
+ To initialize a ``Buffer`` we have to provide an example dataset which defines the columns and dtypes of the data we will be streaming. Next we define the ``length`` to keep the last 100 rows of data. If the data is a DataFrame we can specify whether we will also want to use the ``DataFrame`` ``index``. In this case we will simply define that we want to plot a ``DataFrame`` of 'x' and 'y' positions and a 'count' as ``Points`` and ``Curve`` elements:
68
+
69
+
70
+ ```python
71
+ example = pd.DataFrame({'x': [], 'y': [], 'count': []}, columns=['x', 'y', 'count'])
72
+ dfstream = Buffer(example, length=100, index=False)
73
+ curve_dmap = hv.DynamicMap(hv.Curve, streams=[dfstream])
74
+ point_dmap = hv.DynamicMap(hv.Points, streams=[dfstream])
75
+ ```
76
+
77
+ After applying some styling we will display an ``Overlay`` of the dynamic ``Curve`` and ``Points``
78
+
79
+
80
+ ```python
81
+ (curve_dmap * point_dmap).opts(
82
+ opts.Points(color='count', line_color='black', size=5, padding=0.1, xaxis=None, yaxis=None),
83
+ opts.Curve(line_width=1, color='black'))
84
+ ```
85
+
86
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/brownian.gif"></img>
87
+
88
+ Now that we have set up the ``Buffer`` and defined a ``DynamicMap`` to plot the data we can start pushing data to it. We will define a simple function which simulates brownian motion by accumulating x, y positions. We can ``send`` data through the ``hv.streams.Buffer`` directly.
89
+
90
+
91
+ ```python
92
+ def gen_brownian():
93
+ x, y, count = 0, 0, 0
94
+ while True:
95
+ x += np.random.randn()
96
+ y += np.random.randn()
97
+ count += 1
98
+ yield pd.DataFrame([(x, y, count)], columns=['x', 'y', 'count'])
99
+
100
+ brownian = gen_brownian()
101
+ for i in range(200):
102
+ dfstream.send(next(brownian))
103
+ ```
104
+
105
+ Finally we can clear the data on the stream and plot using the ``clear`` method:
106
+
107
+
108
+ ```python
109
+ dfstream.clear()
110
+ ```
111
+
112
+ Note that when using the ``Buffer`` stream the view will always follow the current range of the data by default, by setting ``buffer.following=False`` or passing following as an argument to the constructor this behavior may be disabled.
113
+
114
+ ## Using the Streamz library
115
+
116
+ Now that we have discovered what ``Pipe`` and ``Buffer`` can do it's time to show how you can use them together with the ``streamz`` library. Although HoloViews does not depend on ``streamz`` and you can use the streaming functionality without needing to learn about it, the two libraries work well together, allowing you to build pipelines to manage continuous streams of data. Streamz is easy to use for simple tasks, but also supports complex pipelines that involve branching, joining, flow control, feedback and more. Here we will mostly focus on connecting streamz output to ``Pipe`` and then ``Buffer`` so for more details about the streamz API, consult the [streamz documentation](https://streamz.readthedocs.io/en/latest/).
117
+
118
+ #### Using ``streamz.Stream`` together with ``Pipe``
119
+
120
+ Let's start with a fairly simple example:
121
+
122
+ 1. Declare a ``streamz.Stream`` and a ``Pipe`` object and connect them into a pipeline into which we can push data.
123
+ 2. Use a ``sliding_window`` of 10, which will first wait for 10 sets of stream updates to accumulate. At that point and for every subsequent update, it will apply ``pd.concat`` to combine the most recent 10 updates into a new dataframe.
124
+ 3. Use the ``sink`` method on the ``streamz.Stream`` to ``send`` the resulting collection of 10 updates to ``Pipe``.
125
+ 4. Declare a ``DynamicMap`` that takes the sliding window of concatenated DataFrames and displays it using a ``Scatter`` Element.
126
+ 5. Color the ``Scatter`` points by their 'count' and set a range, then display:
127
+
128
+
129
+ ```python
130
+ point_source = streamz.Stream()
131
+ pipe = Pipe(data=pd.DataFrame({'x': [], 'y': [], 'count': []}))
132
+ point_source.sliding_window(20).map(pd.concat).sink(pipe.send) # Connect streamz to the Pipe
133
+ scatter_dmap = hv.DynamicMap(hv.Scatter, streams=[pipe])
134
+ ```
135
+
136
+ After set up our streaming pipeline we can again display it:
137
+
138
+
139
+ ```python
140
+ scatter_dmap.opts(bgcolor='black', color='count', ylim=(-4, 4), show_legend=False)
141
+ ```
142
+
143
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz1.gif"></img>
144
+
145
+ There is now a pipeline, but initially this plot will be empty, because no data has been sent to it. To see the plot update, let's use the ``emit`` method of ``streamz.Stream`` to send small chunks of random pandas ``DataFrame``s to our plot:
146
+
147
+
148
+ ```python
149
+ for i in range(100):
150
+ df = pd.DataFrame({'x': np.random.rand(100), 'y': np.random.randn(100), 'count': i},
151
+ columns=['x', 'y', 'count'])
152
+ point_source.emit(df)
153
+ ```
154
+
155
+ #### Using StreamingDataFrame and StreamingSeries
156
+
157
+ The streamz library provides ``StreamingDataFrame`` and ``StreamingSeries`` as a powerful way to easily work with live sources of tabular data. This makes it perfectly suited to work with ``Buffer``. With the ``StreamingDataFrame`` we can easily stream data, apply computations such as cumulative and rolling statistics and then visualize the data with HoloViews.
158
+
159
+ The ``streamz.dataframe`` module provides a ``Random`` utility that generates a ``StreamingDataFrame`` that emits random data with a certain frequency at a specified interval. The ``example`` attribute lets us see the structure and dtypes of the data we can expect:
160
+
161
+
162
+ ```python
163
+ simple_sdf = streamz.dataframe.Random(freq='10ms', interval='100ms')
164
+ print(simple_sdf.index)
165
+ simple_sdf.example.dtypes
166
+ ```
167
+
168
+ Since the ``StreamingDataFrame`` provides a pandas-like API, we can specify operations on the data directly. In this example we subtract a fixed offset and then compute the cumulative sum, giving us a randomly drifting timeseries. We can then pass the x-values of this dataframe to the HoloViews ``Buffer`` and supply ``hv.Curve`` as the ``DynamicMap`` callback to stream the data into a HoloViews ``Curve`` (with the default key and value dimensions):
169
+
170
+
171
+ ```python
172
+ sdf = (simple_sdf-0.5).cumsum()
173
+ hv.DynamicMap(hv.Curve, streams=[Buffer(sdf.x)]).opts(width=500, show_grid=True)
174
+ ```
175
+
176
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz3.gif"></img>
177
+
178
+ The ``Random`` StreamingDataFrame will asynchronously emit events, driving the visualization forward, until it is explicitly stopped, which we can do by calling the ``stop`` method.
179
+
180
+
181
+ ```python
182
+ simple_sdf.stop()
183
+ ```
184
+
185
+ #### Making use of the ``StreamingDataFrame`` API
186
+
187
+ So far we have only computed the cumulative sum, but the ``StreamingDataFrame`` actually has an extensive API that lets us run a broad range of streaming computations on our data. For example, let's apply a rolling mean to our x-values with a window of 500ms and overlay it on top of the 'raw' data:
188
+
189
+
190
+ ```python
191
+ source_df = streamz.dataframe.Random(freq='5ms', interval='100ms')
192
+ sdf = (source_df-0.5).cumsum()
193
+ raw_dmap = hv.DynamicMap(hv.Curve, streams=[Buffer(sdf.x)])
194
+ smooth_dmap = hv.DynamicMap(hv.Curve, streams=[Buffer(sdf.x.rolling('500ms').mean())])
195
+
196
+ (raw_dmap.relabel('raw') * smooth_dmap.relabel('smooth')).opts(
197
+ opts.Curve(width=500, show_grid=True))
198
+ ```
199
+
200
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz4.gif"></img>
201
+
202
+
203
+ ```python
204
+ source_df.stop()
205
+ ```
206
+
207
+ #### Customizing elements with ``functools.partial``
208
+
209
+ In this notebook we have avoided defining custom functions for ``DynamicMap`` by simply supplying the element class and using the element constructor instead. Although this works well for examples, it often won't generalize to real-life situations, because you don't have an opportunity to use anything other than the default dimensions. One simple way to get around this limitation is to use ``functools.partial``:
210
+
211
+
212
+
213
+
214
+ ```python
215
+ from functools import partial
216
+ ```
217
+
218
+ Now you can now easily create an inline callable that creates an element with custom key and value dimensions by supplying them to ``partial`` in the form ``partial(hv.Element, kdims=[...], vdims=[...])``. In the next section, we will see an example of this pattern using ``hv.BoxWhisker``.
219
+
220
+ #### Controlling the length
221
+
222
+ By default the ``Buffer`` accumulates a ``length`` of 1000 samples. In many cases this may be excessive, but we can specify a shorter (or longer) length value to control how much history we accumulate, often depending on the element type.
223
+
224
+ In the following example, a custom ``length`` is used together with a ``partial`` wrapping ``hv.BoxWhisker`` in order to display a cumulative sum generated from a stream of random dataframes:
225
+
226
+
227
+ ```python
228
+ multi_source = streamz.dataframe.Random(freq='50ms', interval='500ms')
229
+ sdf = (multi_source-0.5).cumsum()
230
+ hv.DynamicMap(hv.Table, streams=[Buffer(sdf.x, length=10)]) +\
231
+ hv.DynamicMap(partial(hv.BoxWhisker, kdims=[], vdims='x'), streams=[Buffer(sdf.x, length=100)])
232
+ ```
233
+
234
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz5.gif"></img>
235
+
236
+ Here the given stream ``sdf`` is being consumed by a table showing a short length (where only the items visible in the table need to be kept), along with a plot computing averages and variances over a longer length (100 items).
237
+
238
+ #### Updating multiple cells
239
+
240
+ Since a ``StreamingDataFrame`` will emit data until it is stopped, we can subscribe multiple plots across different cells to the same stream. Here, let's add a ``Scatter`` plot of the same data stream as in the preceding cell:
241
+
242
+
243
+ ```python
244
+ hv.DynamicMap(hv.Scatter, streams=[Buffer(sdf.x)]).redim.label(x='value', index='time')
245
+ ```
246
+
247
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz6.gif"></img>
248
+
249
+ Here we let the ``Scatter`` elements use the column names from the supplied ``DataFrames`` which are relabelled using the ``redim`` method. Stopping the stream will now stop updates to all three of these DynamicMaps:
250
+
251
+
252
+ ```python
253
+ multi_source.stop()
254
+ ```
255
+
256
+ ## Operations over streaming data
257
+
258
+ As we discovered above, the ``Buffer`` lets us set a ``length``, which defines how many rows we want to accumulate. We can use this to our advantage and apply an operation over this length window. In this example we declare a ``Dataset`` and then apply the ``histogram`` operation to compute a ``Histogram`` over the specified ``length`` window:
259
+
260
+
261
+ ```python
262
+ hist_source = streamz.dataframe.Random(freq='5ms', interval='100ms')
263
+ sdf = (hist_source-0.5).cumsum()
264
+ dmap = hv.DynamicMap(hv.Dataset, streams=[Buffer(sdf.x, length=500)])
265
+ hv.operation.histogram(dmap, dimension='x')
266
+ ```
267
+
268
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz7.gif"></img>
269
+
270
+
271
+ ```python
272
+ hist_source.stop()
273
+ ```
274
+
275
+ #### Datashading
276
+
277
+ The same approach will also work for the datashader operation letting us datashade the entire ``length`` window even if we make it very large such as 1 million samples:
278
+
279
+
280
+ ```python
281
+ from holoviews.operation.datashader import datashade
282
+ from bokeh.palettes import Blues8
283
+
284
+ large_source = streamz.dataframe.Random(freq='100us', interval='200ms')
285
+ sdf = (large_source-0.5).cumsum()
286
+ dmap = hv.DynamicMap(hv.Curve, streams=[Buffer(sdf.x, length=1000000)])
287
+ datashade(dmap, streams=[hv.streams.PlotSize], cnorm='linear', cmap=list(Blues8)).opts(width=600)
288
+ ```
289
+
290
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz8.gif"></img>
291
+
292
+
293
+ ```python
294
+ large_source.stop()
295
+ ```
296
+
297
+ ## Asynchronous updates using the tornado ``IOLoop``
298
+
299
+ In most cases, instead of pushing updates manually from the same Python process, you'll want the object to update asynchronously as new data arrives. Since both Jupyter and Bokeh server run on [tornado](http://www.tornadoweb.org/en/stable/), we can use the tornado ``IOLoop`` in both cases to define a non-blocking co-routine that can push data to our stream whenever it is ready. The ``PeriodicCallback`` makes this approach very simple, we simply define a function which will be called periodically with a timeout defined in milliseconds. Once we have declared the callback we can call ``start`` to begin emitting events:
300
+
301
+
302
+ ```python
303
+ from tornado.ioloop import PeriodicCallback
304
+ from tornado import gen
305
+
306
+ count = 0
307
+ buffer = Buffer(np.zeros((0, 2)), length=50)
308
+
309
+ @gen.coroutine
310
+ def f():
311
+ global count
312
+ count += 1
313
+ buffer.send(np.array([[count, np.random.rand()]]))
314
+
315
+ cb = PeriodicCallback(f, 100)
316
+ cb.start()
317
+
318
+ hv.DynamicMap(hv.Curve, streams=[buffer]).opts(padding=0.1, width=600)
319
+ ```
320
+
321
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz2.gif"></img>
322
+
323
+ Since the callback is non-blocking we can continue working in the notebook and execute other cells. Once we're done we can stop the callback by calling ``cb.stop()``.
324
+
325
+
326
+ ```python
327
+ cb.stop()
328
+ ```
329
+
330
+ ## Real examples
331
+
332
+ Using the ``Pipe`` and ``Buffer`` streams we can create complex streaming plots very easily. In addition to the toy examples we presented in this guide it is worth looking at looking at some of the examples using real, live, streaming data.
333
+
334
+ * The [streaming_psutil](http://holoviews.org/gallery/apps/bokeh/streaming_psutil.html) bokeh app is one such example which display CPU and memory information using the ``psutil`` library (install with ``pip install psutil`` or ``conda install psutil``)
335
+
336
+ <img class="gif" src="https://assets.holoviews.org/gifs/guides/user_guide/Streaming_Data/streamz9.gif"></img>
337
+
338
+ As you can see, streaming data works like streams in HoloViews in general, flexibly handling changes over time under either explicit control or governed by some external data source.
hvplot_docs/17-Dashboards.md ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Creating interactive dashboards
2
+
3
+
4
+ ```python
5
+ import pandas as pd
6
+ import holoviews as hv
7
+
8
+ from bokeh.sampledata import stocks
9
+ from holoviews.operation.timeseries import rolling, rolling_outlier_std
10
+
11
+ hv.extension('bokeh')
12
+ ```
13
+
14
+ In the [Data Processing Pipelines section](./14-Data_Pipelines.ipynb) we discovered how to declare a ``DynamicMap`` and control multiple processing steps with the use of custom streams as described in the [Responding to Events](./12-Responding_to_Events.ipynb) guide. A DynamicMap works like a tiny web application, with widgets that select values along a dimension, and a plot that updates. Let's start with a function that loads stock data and see what a DynamicMap can do:
15
+
16
+
17
+ ```python
18
+ def load_symbol(symbol, variable, **kwargs):
19
+ df = pd.DataFrame(getattr(stocks, symbol))
20
+ df['date'] = df.date.astype('datetime64[ns]')
21
+ return hv.Curve(df, ('date', 'Date'), variable).opts(framewise=True)
22
+
23
+ stock_symbols = ['AAPL', 'IBM', 'FB', 'GOOG', 'MSFT']
24
+ variables = ['open', 'high', 'low', 'close', 'volume', 'adj_close']
25
+ dmap = hv.DynamicMap(load_symbol, kdims=['Symbol','Variable'])
26
+ dmap = dmap.redim.values(Symbol=stock_symbols, Variable=variables)
27
+
28
+ dmap.opts(framewise=True)
29
+ rolling(dmap, rolling_window=2)
30
+ ```
31
+
32
+ Here we already have widgets for Symbol and Variable, as those are dimensions in the DynamicMap, but what if we wanted a widget to control the `rolling_window`width value in the HoloViews operation? We could redefine the DynamicMap to include the operation and accept that parameter as another dimension, but in complex cases we would quickly find we need more flexibility in defining widgets and layouts than DynamicMap can give us directly.
33
+
34
+ ## Building dashboards
35
+
36
+ For more flexibility, we can build a full-featured dashboard using the [Panel](https://panel.pyviz.org) library, which is what a DynamicMap is already using internally to generate widgets and layouts. We can easily declare our own custom Panel widgets and link them to HoloViews streams to get dynamic, user controllable analysis workflows.
37
+
38
+ Here, let's start with defining various Panel widgets explicitly, choosing a `RadioButtonGroup` for the `symbol` instead of DynamicMaps's default `Select` widget:
39
+
40
+
41
+ ```python
42
+ import panel as pn
43
+
44
+ symbol = pn.widgets.RadioButtonGroup(options=stock_symbols)
45
+ variable = pn.widgets.Select(options=variables)
46
+ rolling_window = pn.widgets.IntSlider(name='Rolling Window', value=10, start=1, end=365)
47
+
48
+ pn.Column(symbol, variable, rolling_window)
49
+ ```
50
+
51
+ As you can see, these widgets can be displayed but they aren't yet attached to anything, so they don't do much. We can now use ``pn.bind`` to bind the `symbol` and `variable` widgets to the arguments of the DynamicMap callback function, and provide `rolling_window` to the `rolling` operation argument. (HoloViews operations accept Panel widgets or param Parameter values, and they will then update reactively to changes in those widgets.)
52
+
53
+ We can then lay it all out into a simple application that works similarly to the regular DynamicMap display but where we can add our additional widget and control every aspect of the widget configuration and the layout:
54
+
55
+
56
+ ```python
57
+ dmap = hv.DynamicMap(pn.bind(load_symbol, symbol=symbol, variable=variable))
58
+ smoothed = rolling(dmap, rolling_window=rolling_window)
59
+
60
+ app = pn.Row(pn.WidgetBox('## Stock Explorer', symbol, variable, rolling_window),
61
+ smoothed.opts(width=500, framewise=True)).servable()
62
+ app
63
+ ```
64
+
65
+ Here we chose to lay the widgets out into a box to the left of the plot, but we could put the widgets each in different locations, add different plots, etc., to create a full-featured dashboard. See [panel.holoviz.org](https://panel.holoviz.org) for the full set of widgets and layouts supported.
66
+
67
+ Now that we have an app, we can launch it in a separate server if we wish (using `app.show()`), run it as an entirely separate process (`panel serve <thisfilename>.ipynb`, to serve the object marked `servable` above), or export it to a static HTML file (sampling the space of parameter values using "embed"):
68
+
69
+
70
+ ```python
71
+ app.save("dashboard.html", embed=True)
72
+ ```
73
+
74
+ ## Declarative dashboards
75
+
76
+ What if we want our analysis code usable both as a dashboard and also in "headless" contexts such as batch jobs or remote execution? Both Panel and HoloViews are built on the [param](https://param.holoviz.org) library, which lets you capture the definitions and allowable values for your widgets in a way that's not attached to any GUI. That way you can declare all of your attributes and allowed values once, presenting a GUI if you want to explore them interactively or else simply provide specific values if you want batch operation.
77
+
78
+ With this approach, we declare a ``StockExplorer`` class subclassing ``Parameterized`` and defining three parameters, namely the rolling window, the symbol, and the variable to show for that symbol:
79
+
80
+
81
+ ```python
82
+ import param
83
+
84
+ class StockExplorer(param.Parameterized):
85
+
86
+ rolling_window = param.Integer(default=10, bounds=(1, 365))
87
+ symbol = param.ObjectSelector(default='AAPL', objects=stock_symbols)
88
+ variable = param.ObjectSelector(default='adj_close', objects=variables)
89
+
90
+ @param.depends('symbol', 'variable')
91
+ def load_symbol(self):
92
+ df = pd.DataFrame(getattr(stocks, self.symbol))
93
+ df['date'] = df.date.astype('datetime64[ns]')
94
+ return hv.Curve(df, ('date', 'Date'), self.variable).opts(framewise=True)
95
+ ```
96
+
97
+ Here the StockExplorer class will look similar to the Panel code above, defining most of the same information that's in the Panel widgets, but without any dependency on Panel or other GUI libraries; it's simply declaring that this code accepts certain parameter values of the specified types and ranges. These declarations are useful even outside a GUI context, because they allow type and range checking for detecting user errors, but they are also sufficient for creating a GUI later.
98
+
99
+ Instead of using `pn.bind` to bind widget values to functions, here we are declaring that each method depends on the specified parameters, which can be expressed independently of whether there is a widget controlling those parameters; it simply declares (in a way that Panel can utilize) that the given method needs re-running when any of the parameters in that list changes.
100
+
101
+ Now let's use the `load_symbol` method, which already declares which parameters it depends on, as the callback of a DynamicMap and create widgets out of those parameters to build a little GUI:
102
+
103
+
104
+ ```python
105
+ explorer = StockExplorer()
106
+ stock_dmap = hv.DynamicMap(explorer.load_symbol)
107
+ pn.Row(explorer.param, stock_dmap)
108
+ ```
109
+
110
+ Here you'll notice that the `rolling_window` widget doesn't do anything, because it's not connected to anything (e.g., nothing `@param.depends` on it). As we saw in the [Data Processing Pipelines section](./14-Data_Pipelines.ipynb), the ``rolling`` and ``rolling_outlier_std`` operations both accept a ``rolling_window`` parameter, so lets provide that to the operations and display the output of those operations. Finally we compose everything into a panel ``Row``:
111
+
112
+
113
+ ```python
114
+ # Apply rolling mean
115
+ smoothed = rolling(stock_dmap, rolling_window=explorer.param.rolling_window)
116
+
117
+ # Find outliers
118
+ outliers = rolling_outlier_std(stock_dmap, rolling_window=explorer.param.rolling_window).opts(
119
+ color='red', marker='triangle')
120
+
121
+ pn.Row(explorer.param, (smoothed * outliers).opts(width=600))
122
+ ```
123
+
124
+ ## Replacing the output
125
+
126
+ Updating plots using a ``DynamicMap`` is a very efficient means of updating a plot since it will only update the data that has changed. In some cases it is either necessary or more convenient to redraw a plot entirely. ``Panel`` makes this easy by annotating a method with any dependencies that should trigger the plot to be redrawn. In the example below we extend the ``StockExplorer`` by adding a ``datashade`` boolean and a view method which will flip between a datashaded and regular view of the plot:
127
+
128
+
129
+ ```python
130
+ from holoviews.operation.datashader import datashade, dynspread
131
+
132
+ class AdvancedStockExplorer(StockExplorer):
133
+
134
+ datashade = param.Boolean(default=False)
135
+
136
+ @param.depends('datashade')
137
+ def view(self):
138
+ stocks = hv.DynamicMap(self.load_symbol)
139
+
140
+ # Apply rolling mean
141
+ smoothed = rolling(stocks, rolling_window=self.param.rolling_window)
142
+ if self.datashade:
143
+ smoothed = dynspread(datashade(smoothed, aggregator='any')).opts(framewise=True)
144
+
145
+ # Find outliers
146
+ outliers = rolling_outlier_std(stocks, rolling_window=self.param.rolling_window).opts(
147
+ width=600, color='red', marker='triangle', framewise=True)
148
+ return (smoothed * outliers)
149
+ ```
150
+
151
+ In the previous example we explicitly called the ``view`` method, but to allow ``panel`` to update the plot when the datashade parameter is toggled we instead pass it the actual view method. Whenever the datashade parameter is toggled ``panel`` will call the method and update the plot with whatever is returned:
152
+
153
+
154
+ ```python
155
+ explorer = AdvancedStockExplorer()
156
+ pn.Row(explorer.param, explorer.view)
157
+ ```
158
+
159
+ As you can see using streams we have bound the widgets to the streams letting us easily control the stream values and making it trivial to define complex dashboards. For more information on how to deploy bokeh apps from HoloViews and build dashboards see the [Deploying Bokeh Apps](./Deploying_Bokeh_Apps.ipynb) user guide section.
hvplot_docs/Annotators.md ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import holoviews as hv
3
+ import numpy as np
4
+ import panel as pn
5
+
6
+ hv.extension('bokeh')
7
+ ```
8
+
9
+ HoloViews-generated plots generally convey information from Python _to_ a viewer of the plot, but there are also circumstances where information needs to be collected _from_ the viewer and made available for processing in Python:
10
+
11
+ * annotating data with contextual information to aid later interpretation
12
+ * labeling or tagging data for automated machine-learning or other processing pipelines
13
+ * indicating regions of interest, outliers, or other semantic information
14
+ * specifying inputs to a query, command, or simulation
15
+ * testing sensitivity of analyses to adding, changing, or deleting user-selected data points
16
+
17
+ In such cases, it is important to be able to augment, edit, and annotate datasets and to access those values from Python. To perform these actions, HoloViews provides an ``annotate`` helper using [Bokeh's drawing tools](https://docs.bokeh.org/en/latest/docs/reference/models/tools.html#bokeh.models.tools.PointDrawTool) to make it easy to edit HoloViews Elements and add additional information using an associated table. The `annotate` helper:
18
+
19
+ * Adds plot tools that allow editing and adding new elements to a plot
20
+ * Adds table(s) to allow editing the element in a tabular format
21
+ * Returns a layout of these two components
22
+ * Makes the edits, annotations, and selections available on a property of the annotate object so that they can be utilized in Python
23
+
24
+ ## Basics
25
+
26
+ Let us start by annotating a small set of Points. To do this, we need two things:
27
+
28
+ 1. A Points Element to annotate or edit
29
+ 2. An annotator object to collect and store annotations
30
+
31
+ The annotator is a callable object with its own state that can be called to return a Layout consisting of the object to be annotated and an Overlay of editable table(s):
32
+
33
+
34
+ ```python
35
+ points = hv.Points([(0.0, 0.0), (1.0, 1.0), (200000.0, 2000000.0)]).opts(size=10, min_height=500)
36
+
37
+ annotator = hv.annotate.instance()
38
+ layout = annotator(hv.element.tiles.OSM() * points, annotations=['Label'], name="Point Annotations")
39
+
40
+ print(layout)
41
+ ```
42
+
43
+ This layout of a DynamicMap (the user-editable Element data) and an Overlay (the user-editable table) lets a user input the required information:
44
+
45
+
46
+ ```python
47
+ layout
48
+ ```
49
+
50
+ Here we have pre-populated the Element with three points. Each of the points has three bits of information that can be edited using the table: the x location, y location, and a "Label", which was initialized to dummy values when we called `annotate` and asked that there be a `Label` column. Try clicking on one of the rows and editing the location or the label to anything you like. As long as Python is running and the new location is in the viewport, you should see the dot move when you edit the location, and any labels you entered should be visible in the table.
51
+
52
+ You can also edit the locations graphically using the [PointDraw tool](../reference/streams/bokeh/PointDraw.ipynb) in the toolbar:<img src="https://bokeh.pydata.org/en/latest/_images/PointDraw.png">
53
+
54
+ Once you select that tool, you should be able to click and drag any of the existing points and see the location update in the table. Whether you click on the table or the points, the same object should be selected in each, so that you can see how the graphical and tabular representations relate.
55
+
56
+ The PointDraw tool also allows us to add completely new points; once the tool is selected, just click on the plot above in locations not already containing a point and you can see a new point and a new table row appear ready for editing. You can also delete points by selecting them in the plot then pressing Backspace or Delete (depending on operating system).
57
+
58
+ All the above editing and interaction could have been done if we had simply called `hv.annotate(points, annotations=['Label'])` directly, but instead we first saved an "instance" of the annotator object so that we'd also be able to access the resulting data. So, once we are done collecting data from the user, let's use the saved `annotator` object handle to read out the values (by re-evaluating the following line):
59
+
60
+
61
+ ```python
62
+ annotator.annotated.dframe()
63
+ ```
64
+
65
+ You should see that you can access the current set of user-provided or user-modified points and their user-provided labels from within Python, ready for any subsequent processing you need to do.
66
+
67
+ We can also access the currently `selected` points, in case we care only about a subset of the points (which will be empty if no points/rows are selected):
68
+
69
+
70
+ ```python
71
+ annotator.selected.dframe()
72
+ ```
73
+
74
+ ## Configuring the Annotator
75
+
76
+ In addition to managing the list of `annotations`, the `annotate` helper exposes a few additional parameters. Remember like most Param-based objects you can get help about `annotate` parameters using the `hv.help` function:
77
+
78
+
79
+ ```python
80
+ hv.help(hv.annotate)
81
+ ```
82
+
83
+ ### Annotation types
84
+
85
+ The default annotation type is a string, to allow you to put in arbitrary information that you later process in Python. If you want to enforce a more specific type, you can specify the annotation-value types explicitly using a dictionary mapping from column name to the type:
86
+
87
+
88
+ ```python
89
+ hv.annotate(points, annotations={'int': int, 'float': float, 'str': str})
90
+ ```
91
+
92
+ This example also shows how to collect multiple columns of information for the same data point.
93
+
94
+ ## Types of Annotators
95
+
96
+ Currently only a limited set of Elements may be annotated, which include:
97
+
98
+ * ``Points``/``Scatter``
99
+ * ``Curve``
100
+ * ``Path``
101
+ * ``Polygons``
102
+ * ``Rectangles``
103
+
104
+ Adding support for new elements, in most cases, requires adding corresponding drawing/edit tools to Bokeh itself. But if you have data of other types, you may still be able to annotate it by casting it to one of the indicated types, collecting the data, then casting it back.
105
+
106
+ ## Annotating Curves
107
+
108
+ To allow dragging the vertices of the Curve, the ``Curve`` annotator uses the PointDraw tool in the toolbar: <img src="https://bokeh.pydata.org/en/latest/_images/PointDraw.png">
109
+ The vertices will appear when the tool is selected or a vertex is selected in the table. Unlike most other annotators the Curve annotator only allows editing the vertices and does not allow adding new ones.
110
+
111
+
112
+ ```python
113
+ curve = hv.Curve(np.random.randn(50).cumsum())
114
+
115
+ curve_annotator = hv.annotate.instance()
116
+
117
+ curve_annotator(curve.opts(width=800, height=400, responsive=False), annotations={'Label': str})
118
+ ```
119
+
120
+ To access the data you can make use of the ``annotated`` property on the annotator:
121
+
122
+
123
+ ```python
124
+ curve_annotator.annotated.dframe().head(5)
125
+ ```
126
+
127
+ ## Annotating Rectangles
128
+
129
+ The `Rectangles` annotator behaves very similarly to the Points annotator. It allows adding any number of annotation columns, using Bokeh's `BoxEdit` tool that allows both drawing and moving boxes. To see how to use the BoxEdit tool, refer to the HoloViews [BoxEdit stream reference](../reference/streams/bokeh/BoxEdit.ipynb), but briefly:
130
+
131
+ * Select the `BoxEdit` tool in the toolbar: <img src="https://bokeh.pydata.org/en/latest/_images/BoxEdit.png">
132
+ * Click and drag on an existing Rectangle to move it
133
+ * Double click to start drawing a new Rectangle at one corner, and double click to complete the rectangle at the opposite corner
134
+ * Select a rectangle and press the Backspace or Delete key (depending on OS) to delete it
135
+ * Edit the box coordinates in the table to resize it
136
+
137
+
138
+ ```python
139
+ boxes = hv.Rectangles([(0, 0, 1, 1), (1.5, 1.5, 2.5, 2.5)])
140
+
141
+ box_annotator = hv.annotate.instance()
142
+
143
+ box_annotator(boxes.opts(width=800, height=400, responsive=False), annotations=['Label'])
144
+ ```
145
+
146
+ To access the data we can make use of the ``annotated`` property on the annotator instance:
147
+
148
+
149
+ ```python
150
+ box_annotator.annotated.dframe()
151
+ ```
152
+
153
+ ### Annotating paths/polygons
154
+
155
+ Unlike the Points and Boxes annotators, the Path and Polygon annotators allow annotating not just each individual entity but also the vertices that make up the paths and polygons. For more information about using the editing tools associated with this annotator refer to the HoloViews [PolyDraw](../reference/streams/PolyDraw.ipynb) and [PolyEdit](../reference/streams/PolyEdit.ipynb) stream reference guides, but briefly:
156
+
157
+ ##### Drawing/Selecting Deleting Paths/Polygons
158
+
159
+ - Select the PolyDraw tool in the toolbar: <img src="https://bokeh.pydata.org/en/latest/_images/PolyDraw.png">
160
+ - Double click to start a new object, single click to add each vertex, and double-click to complete it.
161
+ - Delete paths/polygons by selecting and pressing Delete key (OSX) or Backspace key (PC)
162
+
163
+ ##### Editing Paths/Polygons
164
+
165
+ - Select the PolyEdit tool in the toolbar: <img src="https://bokeh.pydata.org/en/latest/_images/PolyEdit.png">
166
+ - Double click a Path/Polygon to start editing
167
+ - Drag vertices to edit them, delete vertices by selecting them
168
+
169
+ To edit and annotate the vertices, use the draw tool or the first table to select a particular path/polygon and then navigate to the Vertices tab.
170
+
171
+
172
+ ```python
173
+ path = hv.Path([hv.Box(0, 0, 1), hv.Ellipse(1, 1, 1)])
174
+
175
+ path_annotator = hv.annotate.instance()
176
+
177
+ path_annotator(path.opts(width=800, height=400, responsive=False), annotations=['Label'], vertex_annotations=['Value'])
178
+ ```
179
+
180
+ To access the data we can make use of the iloc method on `Path` objects to access a particular path, and then access the `.data` or convert it to a dataframe:
181
+
182
+
183
+ ```python
184
+ path_annotator.annotated.iloc[0].dframe()
185
+ ```
186
+
187
+ ## Composing Annotators
188
+
189
+ Often we will want to add some annotations on top of one or more other elements which provide context, e.g. when annotating an image with a set of `Points`. As long as only one annotation layer is required you can pass an overlay of multiple elements to the `annotate` operation and it will automatically associate the annotator with the layer that supports annotation. Note however that this will error if multiple elements that support annotation are provided. Below we will annotate a two-photon microscopy image with a set of Points, e.g. to mark the location of each cell:
190
+
191
+
192
+ ```python
193
+ img = hv.Image(np.load('../assets/twophoton.npz')['Calcium'][..., 0])
194
+ cells = hv.Points([]).opts(width=500, height=500, responsive=False, padding=0)
195
+
196
+ hv.annotate(img * cells, annotations=['Label'], name="Cell Annotator")
197
+ ```
198
+
199
+ ### Multiple Annotators
200
+
201
+ If you want to work with multiple annotators in the same plot, you can recompose and rearrange the components returned by each `annotate` helper manually, but doing so can get confusing. To simplify working with multiple annotators at the same time, the `annotate` helper provides a special classmethod that allows composing multiple annotators and other elements, e.g. making a set of tiles into a combined layout consisting of all the components:
202
+
203
+
204
+ ```python
205
+ point_annotate = hv.annotate.instance()
206
+ points = hv.Points([(500000, 500000), (1000000, 1000000)]).opts(size=10, color='red', line_color='black')
207
+ point_layout = point_annotate(points, annotations=['Label'])
208
+
209
+ poly_annotate = hv.annotate.instance()
210
+ poly_layout = poly_annotate(hv.Polygons([]), annotations=['Label'])
211
+
212
+ hv.annotate.compose(hv.element.tiles.OSM(), point_layout, poly_layout)
213
+ ```
214
+
215
+ ## Internals
216
+
217
+ The `annotate` function builds on [Param](https://param.holoviz.org) and [Panel](https://panel.holoviz.org), creating and wrapping Panel `Annotator` panes internally. These objects make it easy to include the annotator in Param-based workflows and trigger actions when parameters change and/or update the annotator in response to external events. The Annotator of a `annotate` instance can be accessed using the `annotator` attribute:
218
+
219
+
220
+ ```python
221
+ print(point_annotate.annotator)
222
+ ```
223
+
224
+ This object can be included directly in a Panel layout, be used to watch for parameter changes, or updated directly. To see the effect of updating directly, uncomment the line below, execute that cell, and then look at the previous plot of Africa, which should get updated with 10 randomly located blue dots.
225
+
226
+
227
+ ```python
228
+ #point_annotate.annotator.object = hv.Points(np.random.randn(10, 2)*1000000).opts(color='blue')
229
+ ```
hvplot_docs/Colormaps.md ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Using colormaps
2
+
3
+ HoloViews supports a wide range of colormaps, each of which allow you to translate numerical data values into visible colors in a plot. Here we will review all the colormaps provided for HoloViews and discuss when and how to use them.
4
+
5
+ The [Styling_Plots](Styling_Plots.ipynb) user guide discusses how to specify any of the colormaps discussed here, using the `cmap` style option:
6
+
7
+
8
+ ```python
9
+ import numpy as np
10
+ import holoviews as hv
11
+ hv.extension('matplotlib')
12
+
13
+ ls = np.linspace(0, 10, 400)
14
+ x,y = np.meshgrid(ls, ls)
15
+ img = hv.Image(np.sin(x)*np.cos(y)+0.1*np.random.rand(400,400),
16
+ bounds=(-20,-20,20,20)).opts(colorbar=True, xaxis=None, yaxis=None)
17
+
18
+ hv.Layout([img.relabel(c).opts(cmap=c) for c in ['gray','PiYG','flag','Set1']])
19
+ ```
20
+
21
+ As you can see, the colormap you choose can dramatically change how your data appears. A well-chosen colormap can help guide the user to notice the features of the data you want to highlight, while a poorly chosen colormap can completely obscure the data and lead to erroneous conclusions. E.g. the low levels of noise present in this data are very difficult to see in A and B, but they completely dominate the plot in C and are visible only at specific (presumably arbitrary) value levels that correspond to color transitions in D. Thus it is important to choose colormaps very carefully!
22
+
23
+ Note that the `cmap` style option used above is applied by the underlying plotting library, not by HoloViews itself. In the above example, Matplotlib uses it as the colormap constructs the image, whereas a Bokeh version of the same plot would provide the colormap to the Bokeh JavaScript code running in the local web browser, which allows the user to control the colormap dynamically in some cases.
24
+
25
+ Colormaps can also be used with the [Datashader `shade()` operation](15-Large_Data.ipynb), in which the provided `cmap` is applied by Datashader to create an image *before* passing the image to the plotting library, which enables additional Datashader features but disables client-side features like colorbars and dynamic colormapping on display.
26
+
27
+ ## Available colormaps
28
+
29
+ As outlined in [Styling_Plots](Styling_Plots.ipynb), you can easily make your own custom colormaps, but it's quite difficult to ensure that a custom map is designed well, so it's generally best to choose an existing, well-tested colormap. Here we will show the many different types of colormaps available, discussing each category and how to use that type of map. The ones shown here are those that are available by name, if the corresponding provider has been installed. E.g. those labelled "(bokeh)" will only be available if Bokeh is installed.
30
+
31
+ Most of these colormaps will work best on *either* a light or a dark background, but not both. To faithfully and intuitively represent monotonically increasing values, you will generally want a colormap where the lowest values are similar in tone to the page background, and higher values become more perceptually salient compared to the page background. To let you match the colormap to the page, the maps listed below have a variant suffixed with `_r` (not shown), which is the same map but with the reverse order.
32
+
33
+
34
+ ```python
35
+ from math import ceil
36
+ from holoviews.plotting.util import process_cmap
37
+
38
+ colormaps = hv.plotting.list_cmaps()
39
+ spacing = np.linspace(0, 1, 64)[np.newaxis]
40
+ opt_kwargs = dict(aspect=6, xaxis=None, yaxis=None, sublabel_format='')
41
+
42
+ def filter_cmaps(category):
43
+ return hv.plotting.util.list_cmaps(records=True,category=category,reverse=False)
44
+
45
+ def cmap_examples(category,cols=4):
46
+ cms = filter_cmaps(category)
47
+ n = len(cms)*1.0
48
+ c=ceil(n/cols) if n>cols else cols
49
+ bars = [hv.Image(spacing, ydensity=1, label="{0} ({1})".format(r.name,r.provider))\
50
+ .opts(cmap=process_cmap(r.name,provider=r.provider), **opt_kwargs)
51
+ for r in cms]
52
+ return hv.Layout(bars).opts(vspace=0.1, hspace=0.1, transpose=(n>cols)).cols(c)
53
+ ```
54
+
55
+ ### Perceptually uniform sequential colormaps
56
+
57
+ Useful for the typical case of having increasing numeric values that you want to distinguish without bias for any specific value. The colormaps in this category are designed to represent similar distances in value space (e.g. a numerical difference from 0.2 to 0.4, or one from 0.4 to 0.6, with similar differences in what we perceive visually).
58
+
59
+ For detailed discussions of this important issue, see
60
+ [Kovesi,](https://arxiv.org/abs/1509.03700)
61
+ [van der Walt & Smith,](https://bids.github.io/colormap) and
62
+ [Karpov,](http://inversed.ru/Blog_2.htm) who each argue for different color spaces and criteria for evaluating colormaps and thus develop different types of colormaps. Despite the disagreements over important details, *all* of the maps here will be significantly more uniform than an arbitrary map designed without perceptual criteria, such as those in "Other Sequential" below, and thus these colormaps represent good default choices in most cases.
63
+
64
+ When choosing one of these, be sure to consider whether you wish your page background to be distinguishable from a color in the colormap. If your data covers the entire plot, then using the background color is fine, but if you need the background color to show through (e.g. to show missing values), then you should avoid maps that include black (`fire`, `magma`, `inferno`, `gray`, `k*`) on a black page or white (`fire`,`gray`) on a white page.
65
+
66
+
67
+ ```python
68
+ cmap_examples('Uniform Sequential')
69
+ ```
70
+
71
+ ### Diverging colormaps
72
+
73
+ Useful to highlight differences from a neutral central value, which is typically chosen to match the page background (e.g. white or yellow when using a white page, or black when using a black page).
74
+
75
+ Most of the diverging maps listed here were *not* developed to match a definition of perceptual uniformity, but those coming from `colorcet` were and should thus be preferred over the rest (which can be obtained by specifying `Uniform Diverging` here).
76
+
77
+ Some of these colormaps include both red and green, making them ambiguous for people with the most common types of colorblindness, and should thus be avoided where possible.
78
+
79
+
80
+ ```python
81
+ cmap_examples('Diverging')
82
+ ```
83
+
84
+ ### Rainbow colormaps
85
+
86
+ Rainbow-like colormaps convey value primarily through color rather than luminance. They result in eye-catching plots, but because rainbow colors form a continuous, cyclic spectrum, they can be ambiguous about which values are higher than the others. Most of them are also highly perceptually non-uniform, with pronounced banding that makes some values easily distinguished from their neighbors, and other wide ranges of values nearly indistinguishable (e.g. the greenish colors in the `gist_rainbow` and `jet` colormaps).
87
+
88
+ If you do want a rainbow colormap, please consider using one of the three perceptually uniform versions (category `Uniform Rainbow`) included here:
89
+
90
+ - `colorwheel` (colorcet): for cyclic values like angles and longitudes that wrap around to the same value at the end of the range (notice that the lowest and highest colors are both blue)
91
+ - `rainbow` (colorcet): for monotonically and uniformly increasing values (skips purple region to avoid ordering ambiguity)
92
+ - `isolum` (colorcet): for monotonically and uniformly increasing values, but only uses hue changes, with a constant lightness. Nearly all the other maps are dominated by changes in lightness, which is much more perceptually salient than strict changes in hue as in this map. Useful as a counterpart and illustration of the role of lightness.
93
+
94
+ Of course, rainbow colormaps have the disadvantage that they are inherently unsuitable for most colorblind viewers, because they require viewers to distinguish between red and green to determine value.
95
+
96
+
97
+ ```python
98
+ cmap_examples('Rainbow')
99
+ ```
100
+
101
+ ### Categorical colormaps
102
+
103
+ Primarily useful as color cycles rather than colormaps, i.e. as a list of discrete color values, not a continuous range of colors. Will produce discrete banding when used on continuous values, like in a geographic contour plot, but if that effect is desired it's probably better to use `color_levels` with a sequential colormap to be able to control how many levels there are and give them a natural ordering.
104
+
105
+ Most of these color sets are constructed by hand, with a relatively small number of distinct colors. If you want a larger number of colors, the `glasbey_` categorical maps from Colorcet are generated using a systematic procedure based on sampling a perceptual space for widely separated colors, which allows large numbers of categories to be distinguished from each other.
106
+
107
+ The `glasbey_hv` colors have the useful property that they share the same first 12 colors as the default HoloViews color cycle, which means that if you want the same colors as usual but with more available when needed, you can switch the HoloViews default using `hv.Cycle.default_cycles['default_colors']=colorcet.glasbey_hv`.
108
+
109
+
110
+ ```python
111
+ cmap_examples('Categorical')
112
+ ```
113
+
114
+ ### Mono Sequential colormaps
115
+
116
+ Monotonically increasing values that serve the same purpose as [Uniform Sequential](#Perceptually-uniform-sequential-colormaps) (above), but are not specifically constructed to be perceptually uniform. Useful when you want to fit into a particular visual theme or color scheme, or when you want to color entire plots differently from other entire plots (e.g. to provide a visual "traffic light" indicator for the entire plot, making some plots stand out relative to others). If you just need a single colormap, try to select a Uniform Sequential map instead of these.
117
+
118
+
119
+ ```python
120
+ cmap_examples('Mono Sequential')
121
+ ```
122
+
123
+ ### Other Sequential colormaps
124
+
125
+ Other sequential colormaps are included, but are not meant for general use. Some of these have a very high degree of perceptual non-uniformity, making them highly misleading. E.g. the `hot` (matplotlib) colormap includes pronounced banding (with sharp perceptual discontinuities and long stretches of indistinguishable colors); consider using the perceptually uniform `fire` (colorcet) map instead. Others like `gray` largely duplicate maps in the other categories above, and so can cause confusion. The Uniform Sequential maps, or if necessary Mono Sequential, are generally good alternatives to these.
126
+
127
+
128
+ ```python
129
+ cmap_examples('Other Sequential')
130
+ ```
131
+
132
+ ### Miscellaneous colormaps
133
+
134
+ There are a variety of other colormaps not fitting into the categories above, mostly of limited usefuless. Exceptions include the `flag` and `prism` (matplotlib) colormaps that could be useful for highlighting local changes in value (details), with no information about global changes in value (due to the repeating colors).
135
+
136
+
137
+ ```python
138
+ cmap_examples('Miscellaneous')
139
+ ```
140
+
141
+ See the [Styling_Plots](Styling_Plots.ipynb) user guide for how these colormaps can be used to control how your data is plotted.
142
+
143
+ ## Querying and filtering the list of colormaps
144
+
145
+ For most purposes, you can just pick one of the colormaps above for a given plot. However, HoloViews is very often used to build applications and dashboards, many of which include a "colormap" selection widget. Because there are so many colormaps available, most of which are inappropriate for any specific plot, it's useful to be able to pull up a list of all the colormaps that are suitable for the specific type of plot used in the app.
146
+
147
+ To allow such filtering, HoloViews stores the following information about each named colormap, matched by substring:
148
+
149
+ - **name**: string name for the colormap
150
+ - **category**: Type of map by intended use or purpose ('[Uniform|Mono|Other ]Sequential', '[Uniform ]Diverging', '[Uniform ]Rainbow', '[Uniform ]Categorical', or 'Miscellaneous')
151
+ - **provider**: package offering the colormap ('matplotlib', 'bokeh', or 'colorcet')
152
+ - **source**: original source or creator of the colormaps ('cet', 'colorbrewer', 'd3', 'bids','misc')
153
+ - **bg**: base/background color expected for the map ('light','dark','medium','any')
154
+ - **reverse**: whether the colormap name includes `_r` indicating that it is a reverse of a base map (True, False)
155
+
156
+ The `hv.plotting.list_cmaps()` function used above can select a subset of the available colormaps by filtering based on the above values:
157
+
158
+ ```
159
+ list_cmaps(provider, records, name, category, source, bg, reverse)
160
+ ```
161
+
162
+ The examples below should make it clear how to use this function.
163
+
164
+
165
+ ```python
166
+ from holoviews.plotting import list_cmaps
167
+ def format_list(l):
168
+ print(' '.join(sorted([k for k in l])))
169
+ ```
170
+
171
+ All named colormaps provided by `colorcet`, reversed:
172
+
173
+
174
+ ```python
175
+ format_list(list_cmaps(provider='colorcet',reverse=True))
176
+ ```
177
+
178
+ All non-reversed colormaps provided by `matplotlib` and originating from `d3` that have `20` in their names:
179
+
180
+
181
+ ```python
182
+ format_list(list_cmaps(name='20', source='d3', provider='matplotlib', reverse=False))
183
+ ```
184
+
185
+ Colormaps provided by Bokeh that are suitable for dark-colored (e.g. black) backgrounds:
186
+
187
+
188
+ ```python
189
+ format_list(list_cmaps(category='Other Sequential', bg='dark'))
190
+ ```
191
+
192
+ Notice how some of these have `_r`, because those two natively start with light values and must be reversed to be suitable on a dark background. In this case the results for `bg='light'` are the complementary set of colormaps:
193
+
194
+
195
+ ```python
196
+ format_list(list_cmaps(category='Other Sequential', bg='light'))
197
+ ```
198
+
199
+ However, `Diverging` colormaps do not change their background color when reversed, and so requesting a light or dark background gives different maps altogether (depending on their central color):
200
+
201
+
202
+ ```python
203
+ format_list(list_cmaps(category='Diverging', bg='dark'))
204
+ ```
205
+
206
+
207
+ ```python
208
+ format_list(list_cmaps(category='Diverging', bg='light'))
209
+ ```
210
+
211
+ Matches are done by substring, so all sequential colormaps suitable for `dark` or `any` backgrounds can be obtained with:
212
+
213
+
214
+ ```python
215
+ format_list(list_cmaps(category='Sequential', bg='a'))
216
+ ```
217
+
218
+ In the examples above, `list_cmaps` is returning just the colormap name, but if you want to work with the filter information yourself to do more complex queries, you can ask that it return the full records as namedtuples instead:
219
+
220
+
221
+ ```python
222
+ list_cmaps(category="Uniform Sequential", provider='bokeh', bg='light', records=True)
223
+ ```
224
+
225
+ In addition to populating GUI widgets, another way to use this filtering is to systematically evaluate how your plot will look with a variety of different colormaps of the same type:
226
+
227
+
228
+ ```python
229
+ hv.Layout([img.relabel(c).opts(cmap=c, colorbar=False, sublabel_format='')
230
+ for c in list_cmaps(category='Diverging', bg='light', reverse=False)][:14])\
231
+ .opts(vspace=0.1, hspace=0.1).cols(7)
232
+ ```
233
+
234
+ You could also consider filtering on the actual values in the colormap, perhaps to ensure that the specific background color you are using is not present in the colormap. For this you can use the `hv.plotting.util.process_cmap` function to look up the actual colormap values by name and provider.
hvplot_docs/Continuous_Coordinates.md ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Continuous Coordinates
2
+
3
+ HoloViews is designed to work with scientific and engineering data, which is often in the form of discrete samples from an underlying continuous system. Imaging data is one clear example: measurements taken at a regular interval over a grid covering a two-dimensional area. Although the measurements are discrete, they approximate a continuous distribution, and HoloViews provides extensive support for working naturally with data of this type.
4
+
5
+ ## 2D Continuous spaces
6
+
7
+ In this user guide we will show the support provided by HoloViews for working with two-dimensional regularly sampled grid data like images, and then in subsequent sections discuss how HoloViews supports one-dimensional, higher-dimensional, and irregularly sampled data with continuous coordinates.
8
+
9
+
10
+ ```python
11
+ import numpy as np
12
+ import holoviews as hv
13
+ from holoviews import opts
14
+
15
+ hv.extension('bokeh')
16
+
17
+ np.set_printoptions(precision=2, linewidth=80)
18
+ opts.defaults(opts.Layout(shared_axes=False))
19
+ ```
20
+
21
+ First, let's consider:
22
+
23
+ |||
24
+ |:--------------:|:----------------|
25
+ | **``f(x,y)``** | a simple function that accepts a location in a 2D plane specified in millimeters (mm) |
26
+ | **``region``** | a 1mm&times;1mm square region of this 2D plane, centered at the origin, and |
27
+ | **``coords``** | a function returning a square (s&times;s) grid of (x,y) coordinates regularly sampling the region in the given bounds, at the centers of each grid cell |
28
+ ||||
29
+
30
+
31
+
32
+
33
+ ```python
34
+ def f(x,y):
35
+ return x+y/3.1
36
+
37
+ region=(-0.5,-0.5,0.5,0.5)
38
+
39
+ def coords(bounds,samples):
40
+ l,b,r,t=bounds
41
+ hc=0.5/samples
42
+ return np.meshgrid(np.linspace(l+hc,r-hc,samples),
43
+ np.linspace(b+hc,t-hc,samples))
44
+ ```
45
+
46
+ Now let's build a Numpy array regularly sampling this function at a density of 5 samples per mm:
47
+
48
+
49
+ ```python
50
+ f5=f(*coords(region,5))
51
+ f5
52
+ ```
53
+
54
+ We can visualize this array (and thus the function ``f``) either using a ``Raster``, which uses the array's own integer-based coordinate system (which we will call "array" coordinates), or an ``Image``, which uses a continuous coordinate system, or as a ``HeatMap`` labelling each value explicitly:
55
+
56
+
57
+ ```python
58
+ r5 = hv.Raster(f5, label="R5")
59
+ i5 = hv.Image( f5, label="I5", bounds=region)
60
+ h5 = hv.HeatMap([(x, y, round(f5[4-y,x],2)) for x in range(0,5) for y in range(0,5)], label="H5")
61
+
62
+ h5_labels = hv.Labels(h5).opts(padding=0)
63
+
64
+ r5 + i5 + h5*h5_labels
65
+ ```
66
+
67
+ Both the ``Raster`` and ``Image`` ``Element`` types accept the same input data and show the same arrangement of colors, but a visualization of the ``Raster`` type reveals the underlying raw array indexing, while the ``Image`` type has been labelled with the coordinate system from which we know the data has been sampled. All ``Image`` operations work with this continuous coordinate system instead, while the corresponding operations on a ``Raster`` use raw array indexing.
68
+
69
+ For instance, all five of these indexing operations refer to the same element of the underlying Numpy array, i.e. the second item in the first row:
70
+
71
+
72
+ ```python
73
+ "r5[0,1]=%0.2f r5.data[0,1]=%0.2f i5[-0.2,0.4]=%0.2f i5[-0.24,0.37]=%0.2f i5.data[0,1]=%0.2f" % \
74
+ (r5[1,0], r5.data[0,1], i5[-0.2,0.4], i5[-0.24,0.37], i5.data[0,1])
75
+ ```
76
+
77
+ You can see that the ``Raster`` and the underlying ``.data`` elements both use Numpy's raw integer indexing, while the ``Image`` uses floating-point values that are then mapped onto the appropriate array element.
78
+
79
+ This diagram should help show the relationships between the ``Raster`` coordinate system in the plot (which ranges from 0 at the top edge to 5 at the bottom), the underlying raw Numpy integer array indexes (labelling each dot in the **Array coordinates** figure), and the underlying **Continuous coordinates**:
80
+
81
+ <TABLE style='border:5'>
82
+ <TR>
83
+ <TH><CENTER>Array coordinates</CENTER></TH>
84
+ <TH><CENTER>Continuous coordinates</CENTER></TH>
85
+ </TR>
86
+ <TR>
87
+ <TD><IMG src="http://ioam.github.io/topographica/_images/matrix_coords.png"></TD>
88
+ <TD><IMG src="http://ioam.github.io/topographica/_images/sheet_coords_-0.2_0.4.png"></TD>
89
+ </TR>
90
+ </TABLE>
91
+
92
+ Importantly, although we used a 5&times;5 array in this example, we could substitute a much larger array with the same continuous coordinate system if we wished, without having to change any of our continuous indexes -- they will still point to the correct location in the continuous space:
93
+
94
+
95
+ ```python
96
+ f10=f(*coords(region,10))
97
+ f10
98
+ ```
99
+
100
+
101
+ ```python
102
+ r10 = hv.Raster(f10, label="R10")
103
+ i10 = hv.Image(f10, label="I10", bounds=region)
104
+ r10+i10
105
+ ```
106
+
107
+ The image now has higher resolution, but still visualizes the same underlying continuous function, now evaluated at 100 grid positions instead of 25:
108
+
109
+ <TABLE style='border:5'>
110
+ <TR>
111
+ <TH><CENTER>Array coordinates</CENTER></TH>
112
+ <TH><CENTER>Continuous coordinates</CENTER></TH>
113
+ </TR>
114
+ <TR>
115
+ <TD><IMG src="http://ioam.github.io/topographica/_images/matrix_coords_hidensity.png"></TD>
116
+ <TD><IMG src="http://ioam.github.io/topographica/_images/sheet_coords_-0.2_0.4.png"></TD>
117
+ </TR>
118
+ </TABLE>
119
+
120
+ Indexing the exact same coordinates as above now gets very different results:
121
+
122
+
123
+ ```python
124
+ "r10[0,1]=%0.2f r10.data[0,1]=%0.2f i10[-0.2,0.4]=%0.2f i10[-0.24,0.37]=%0.2f i10.data[0,1]=%0.2f" % \
125
+ (r10[1,0], r10.data[0,1], i10[-0.2,0.4], i10[-0.24,0.37], i10.data[0,1])
126
+ ```
127
+
128
+ The array-based indexes used by ``Raster`` and the Numpy array in ``.data`` still return the second item in the first row of the array, but this array element now corresponds to location (-0.35,0.4) in the continuous function, and so the value is different. These indexes thus do *not* refer to the same location in continuous space as they did for the other array density, because raw Numpy-based indexing is *not* independent of density or resolution.
129
+
130
+ Luckily, the two continuous coordinates still return very similar values to what they did before, since they always return the value of the array element corresponding to the closest location in continuous space. They now return elements just above and to the right, or just below and to the left, of the earlier location, because the array now has a higher resolution with elements centered at different locations.
131
+
132
+ Indexing in continuous coordinates always returns the value closest to the requested value, given the available resolution. Note that in the case of coordinates truly on the boundary between array elements (as for -0.2,0.4), the bounds of each array cell are taken as right exclusive and upper exclusive, and so (-0.2,0.4) returns array index (3,0).
133
+
134
+ ## Slicing in 2D
135
+
136
+ In addition to indexing (looking up a value), slicing (selecting a region) works as expected in continuous space (see the [Indexing and Selecting](10-Indexing_and_Selecting.ipynb) user guide for more explanation). For instance, we can ask for a slice from (-0.275,-0.0125) to (0.025,0.2885) in continuous coordinates:
137
+
138
+
139
+ ```python
140
+ sl10=i10[-0.275:0.025,-0.0125:0.2885]
141
+ sl10.data
142
+ ```
143
+
144
+
145
+ ```python
146
+ sl10
147
+ ```
148
+
149
+ This slice has selected those array elements whose centers are contained within the specified continuous space. To do this, the continuous coordinates are first converted by HoloViews into the floating-point range (5.125,2.250) (2.125,5.250) of array coordinates, and all those elements whose centers are in that range are selected:
150
+
151
+ <TABLE style='border:5'>
152
+ <TR>
153
+ <TH><CENTER>Array coordinates</CENTER></TH>
154
+ <TH><CENTER>Continuous coordinates</CENTER></TH>
155
+ </TR>
156
+ <TR>
157
+ <TD><IMG src="http://ioam.github.io/topographica/_images/connection_field.png"></TD>
158
+ <TD><IMG src="http://ioam.github.io/topographica/_images/sheet_coords_-0.275_-0.0125_0.025_0.2885.png"></TD>
159
+ </TR>
160
+ </TABLE>
161
+
162
+ Slicing also works for ``Raster`` elements, but it results in an object that always reflects the contents of the underlying Numpy array (i.e., always with the upper left corner labelled 0,0):
163
+
164
+
165
+ ```python
166
+ r5[0:3,1:3] + r5[0:3,1:2]
167
+ ```
168
+
169
+ Hopefully these examples make it clear that if you are using data that is sampled from some underlying continuous system, you should use the continuous coordinates offered by HoloViews objects like ``Image`` so that your programs can be independent of the resolution or sampling density of that data, and so that your axes and indexes can be expressed naturally, using the actual units of the underlying continuous space. The data will still be stored in the same Numpy array, but now you can treat it consistently like the approximation to continuous values that it is.
170
+
171
+ ## 1D and nD Continuous coordinates
172
+
173
+ All of the above examples use the common case for visualizations of a two-dimensional regularly gridded continuous space, which is implemented in ``holoviews.core.sheetcoords.SheetCoordinateSystem``.
174
+
175
+ Similar continuous coordinates and slicing are also supported for ``Chart`` elements, such as ``Curve``s, but using a single index and allowing arbitrary irregular spacing, implemented in ``holoviews.elements.chart.Chart``.
176
+
177
+ They also work the same for the n-dimensional coordinates and slicing supported by the [container](Containers) types ``HoloMap``, ``NdLayout``, and ``NdOverlay``, implemented in ``holoviews.core.dimension.Dimensioned`` and again allowing arbitrary irregular spacing.
178
+
179
+ ``QuadMesh`` elements are similar but allow more general types of mapping between the underlying array and the continuous space, with arbitrary spacing along each of the axes or even over the entire array. See the ``QuadMesh`` element for more details.
180
+
181
+ Together, these powerful continuous-coordinate indexing and slicing operations allow you to work naturally and simply in the full *n*-dimensional space that characterizes your data and parameter values.
182
+
183
+ ## Sampling
184
+
185
+ The above examples focus on indexing and slicing, but as described in the [Indexing and Selecting](10-Indexing_and_Selecting.ipynb) user guide there is another related operation supported for continuous spaces, called sampling. Sampling is similar to indexing and slicing, in that all of them can reduce the dimensionality of your data, but sampling is implemented in a general way that applies for any of the 1D, 2D, or nD datatypes. For instance, if we take our 10&times;10 array from above, we can ask for the value at a given location, which will come back as a ``Table``, i.e. a dictionary with one (key,value) pair:
186
+
187
+
188
+ ```python
189
+ e10=i10.sample(x=-0.275, y=0.2885)
190
+ e10.opts(height=75)
191
+ ```
192
+
193
+ Similarly, if we ask for the value of a given *y* location in continuous space, we will get a ``Curve`` with the array row closest to that *y* value in the ``Image`` 2D array returned as arrays of `x` values and the corresponding *z* value from the image:
194
+
195
+
196
+ ```python
197
+ r10=i10.sample(y=0.2885)
198
+ r10
199
+ ```
200
+
201
+ The same sampling syntax can be used on HoloViews objects with any number of continuous-coordinate dimensions, in each case returning a HoloViews object of the correct dimensionality. This support for working in continuous spaces makes it much more natural to work with HoloViews objects than directly with the underlying raw Numpy arrays, but the raw data always remains available when needed.
hvplot_docs/Customization.md ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import hvplot
3
+ import hvplot.pandas # noqa
4
+ ```
5
+
6
+ The hvPlot API is closely modeled on the pandas plot API but also diverges in certain cases, either to improve consistency or to provide additional functionality. This section will outline the valid options to control the axes of a plot, to control datashading and to modify the style of a plot. To look these options up interactively you may either use the tab-completion machinery in IPython or the Jupyter notebook, e.g.:
7
+
8
+ ```python
9
+ df.hvplot.line(<TAB>
10
+ ```
11
+
12
+ OR use the help method:
13
+
14
+ ```
15
+ hvplot.help('line')
16
+ ```
17
+
18
+ ## Generic options
19
+
20
+ The generic set of options which may apply to all plot types include:
21
+
22
+ clim: tuple
23
+ Lower and upper bound of the color scale
24
+ cnorm (default='linear'): str
25
+ Color scaling which must be one of 'linear', 'log' or 'eq_hist'
26
+ colorbar (default=False): boolean
27
+ Enables a colorbar
28
+ fontscale: number
29
+ Scales the size of all fonts by the same amount, e.g. fontscale=1.5
30
+ enlarges all fonts (title, xticks, labels etc.) by 50%
31
+ fontsize: number or dict
32
+ Set title, label and legend text to the same fontsize. Finer control
33
+ by using a dict: {'title': '15pt', 'ylabel': '5px', 'ticks': 20}
34
+ flip_xaxis/flip_yaxis: boolean
35
+ Whether to flip the axis left to right or up and down respectively
36
+ grid (default=False): boolean
37
+ Whether to show a grid
38
+ hover : boolean
39
+ Whether to show hover tooltips, default is True unless datashade is
40
+ True in which case hover is False by default
41
+ hover_cols (default=[]): list or str
42
+ Additional columns to add to the hover tool or 'all' which will
43
+ includes all columns (including indexes if use_index is True).
44
+ invert (default=False): boolean
45
+ Swaps x- and y-axis
46
+ frame_width/frame_height: int
47
+ The width and height of the data area of the plot
48
+ legend (default=True): boolean or str
49
+ Whether to show a legend, or a legend position
50
+ ('top', 'bottom', 'left', 'right')
51
+ logx/logy (default=False): boolean
52
+ Enables logarithmic x- and y-axis respectively
53
+ logz (default=False): boolean
54
+ Enables logarithmic colormapping
55
+ loglog (default=False): boolean
56
+ Enables logarithmic x- and y-axis
57
+ max_width/max_height: int
58
+ The maximum width and height of the plot for responsive modes
59
+ min_width/min_height: int
60
+ The minimum width and height of the plot for responsive modes
61
+ padding: number or tuple
62
+ Fraction by which to increase auto-ranged extents to make
63
+ datapoints more visible around borders. Supports tuples to
64
+ specify different amount of padding for x- and y-axis and
65
+ tuples of tuples to specify different amounts of padding for
66
+ upper and lower bounds.
67
+ responsive: boolean
68
+ Whether the plot should responsively resize depending on the
69
+ size of the browser. Responsive mode will only work if at
70
+ least one dimension of the plot is left undefined, e.g. when
71
+ width and height or width and aspect are set the plot is set
72
+ to a fixed size, ignoring any responsive option.
73
+ rot: number
74
+ Rotates the axis ticks along the x-axis by the specified
75
+ number of degrees.
76
+ shared_axes (default=True): boolean
77
+ Whether to link axes between plots
78
+ transforms (default={}): dict
79
+ A dictionary of HoloViews dim transforms to apply before plotting
80
+ title (default=''): str
81
+ Title for the plot
82
+ tools (default=[]): list
83
+ List of tool instances or strings (e.g. ['tap', box_select'])
84
+ xaxis/yaxis: str or None
85
+ Whether to show the x/y-axis and whether to place it at the
86
+ 'top'/'bottom' and 'left'/'right' respectively.
87
+ xformatter/yformatter (default=None): str or TickFormatter
88
+ Formatter for the x-axis and y-axis (accepts printf formatter,
89
+ e.g. '%.3f', and bokeh TickFormatter)
90
+ xlabel/ylabel/clabel (default=None): str
91
+ Axis labels for the x-axis, y-axis, and colorbar
92
+ xlim/ylim (default=None): tuple or list
93
+ Plot limits of the x- and y-axis
94
+ xticks/yticks (default=None): int or list
95
+ Ticks along x- and y-axis specified as an integer, list of
96
+ ticks positions, or list of tuples of the tick positions and labels
97
+ width (default=700)/height (default=300): int
98
+ The width and height of the plot in pixels
99
+ attr_labels (default=None): bool
100
+ Whether to use an xarray object's attributes as labels, defaults to
101
+ None to allow best effort without throwing a warning. Set to True
102
+ to see warning if the attrs can't be found, set to False to disable
103
+ the behavior.
104
+ sort_date (default=True): bool
105
+ Whether to sort the x-axis by date before plotting
106
+ symmetric (default=None): bool
107
+ Whether the data are symmetric around zero. If left unset, the data
108
+ will be checked for symmetry as long as the size is less than
109
+ ``check_symmetric_max``.
110
+ check_symmetric_max (default=1000000):
111
+ Size above which to stop checking for symmetry by default on the data.
112
+
113
+ ## Datashading options
114
+
115
+ In addition to regular plot options hvplot also exposes options for dealing with large data:
116
+
117
+ aggregator (default=None):
118
+ Aggregator to use when applying rasterize or datashade operation
119
+ (valid options include 'mean', 'count', 'min', 'max' and more, and
120
+ datashader reduction objects)
121
+ dynamic (default=True):
122
+ Whether to return a dynamic plot which sends updates on widget and
123
+ zoom/pan events or whether all the data should be embedded
124
+ (warning: for large groupby operations embedded data can become
125
+ very large if dynamic=False)
126
+ datashade (default=False):
127
+ Whether to apply rasterization and shading using datashader
128
+ library returning an RGB object
129
+ dynspread (default=False):
130
+ Allows plots generated with datashade=True to increase the point
131
+ size to make sparse regions more visible
132
+ rasterize (default=False):
133
+ Whether to apply rasterization using the datashader library
134
+ returning an aggregated Image
135
+ x_sampling/y_sampling (default=None):
136
+ Specifies the smallest allowed sampling interval along the x/y axis.
137
+
138
+ ## Geographic options
139
+
140
+ When dealing with geographic data, there are a number of options that become available. See the [geographic section](Geographic_Data.ipynb) for more information on working with geographic data:
141
+
142
+ coastline (default=False):
143
+ Whether to display a coastline on top of the plot, setting
144
+ coastline='10m'/'50m'/'110m' specifies a specific scale.
145
+ crs (default=None):
146
+ Coordinate reference system of the data specified as Cartopy
147
+ CRS object, proj.4 string or EPSG code.
148
+ features (default=None): dict or list
149
+ A list of features or a dictionary of features and the scale
150
+ at which to render it. Available features include 'borders',
151
+ 'coastline', 'lakes', 'land', 'ocean', 'rivers' and 'states'.
152
+ Available scales include '10m'/'50m'/'110m'.
153
+ geo (default=False):
154
+ Whether the plot should be treated as geographic (and assume
155
+ PlateCarree, i.e. lat/lon coordinates).
156
+ global_extent (default=False):
157
+ Whether to expand the plot extent to span the whole globe.
158
+ project (default=False):
159
+ Whether to project the data before plotting (adds initial
160
+ overhead but avoids projecting data when plot is dynamically
161
+ updated).
162
+ tiles (default=False):
163
+ Whether to overlay the plot on a tile source. Tiles sources
164
+ can be selected by name or a tiles object or class can be passed,
165
+ the default is 'Wikipedia'.
166
+
167
+ ## Kind options
168
+
169
+ Each type of plot may have a number of options to visual attributes specific to that plot type. In general these are provided in the docstring of the plot type, which can be viewed using ``help`` method:
170
+
171
+
172
+ ```python
173
+ hvplot.help('scatter', generic=False, style=False)
174
+ ```
175
+
176
+ ## Styling options
177
+
178
+ Beyond the options specific to each plot type (or ``kind``) it is also possible to customize each component in detail, exposing all the options bokeh exposes. These usually include options to color the line and fill color, alpha and style. To see the full listing we can once again use the ``help`` method:
179
+
180
+
181
+ ```python
182
+ hvplot.help('line', docstring=False, generic=False)
183
+ ```
184
+
185
+ In general, the objects returned by hvPlot are regular HoloViews objects, which can be overlaid, laid out, composed and customized like all other HoloViews objects. The [HoloViews](https://holoviews.org) website explains all the functionality available, but what's on this hvPlot website should be enough to get you up and running for typical usage.
hvplot_docs/Customizing_Plots.md ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Customizing Plots
2
+
3
+
4
+ ```python
5
+ import numpy as np
6
+ import holoviews as hv
7
+ from holoviews import dim, opts
8
+
9
+ hv.extension('bokeh', 'matplotlib')
10
+ ```
11
+
12
+ The HoloViews options system allows controlling the various attributes of a plot. While different plotting extensions like bokeh, matplotlib and plotly offer different features and the style options may differ, there are a wide array of options and concepts that are shared across the different extensions. Specifically this guide provides an overview on controlling the various aspects of a plot including titles, axes, legends and colorbars.
13
+
14
+ Plots have an overall hierarchy and here we will break down the different components:
15
+
16
+ * [**Plot**](#customizing-the-plot): Refers to the overall plot which can consist of one or more axes
17
+ - [Titles](#title): Using title formatting and providing custom titles
18
+ - [Background](#background): Setting the plot background color
19
+ - [Font sizes](#font-sizes): Controlling the font sizes on a plot
20
+ - [Legends](#legend-customization): Controlling the position and styling of the legend
21
+ - [Plot hooks](#plot-hooks): Using custom hooks to modify plots
22
+ * [**Axes**](#customizing-axes): A set of axes provides scales describing the mapping between data and the space on screen
23
+ - [Types of axes](#types-of-axes):
24
+ - [Linear axes](#linear-axes)
25
+ - [Logarithmic axes](#log-axes)
26
+ - [Datetime axes](#datetime-axes)
27
+ - [Categorical axes](#categorical-axes)
28
+ - [Axis position](#axis-position): Positioning and hiding axes
29
+ - [Inverting axes](#inverting-axes): Flipping the x-/y-axes and inverting an axis
30
+ - [Axis labels](#axis-labels): Setting axis labels using dimensions and options
31
+ - [Axis ranges](#axis-ranges): Controlling axes ranges using dimensions, padding and options
32
+ - [Axis ticks](#axis-ticks): Controlling axis tick locations, labels and formatting
33
+ - [Twin axes](#twin-axes): Enabling twin axes
34
+
35
+ ## Customizing the plot
36
+
37
+ ### Title
38
+
39
+ A plot's title is usually constructed using a formatter which takes the group and label along with the plots dimensions into consideration. The default formatter is:
40
+
41
+ '{label} {group} {dimensions}'
42
+
43
+ where the ``{label}`` and ``{group}`` are inherited from the objects group and label parameters and ``dimensions`` represent the key dimensions in a HoloMap/DynamicMap:
44
+
45
+
46
+ ```python
47
+ hv.HoloMap({i: hv.Curve([1, 2, 3-i], group='Group', label='Label') for i in range(3)}, 'Value')
48
+ ```
49
+
50
+ The title formatter may however be overridden with an explicit title, which may include any combination of the three formatter variables:
51
+
52
+
53
+ ```python
54
+ hv.Curve([1, 2, 3]).opts(title="Custom Title")
55
+ ```
56
+
57
+ ### Background
58
+
59
+ Another option which can be controlled at the level of a plot is the background color which may be set using the `bgcolor` option:
60
+
61
+
62
+ ```python
63
+ hv.Curve([1, 2, 3]).opts(bgcolor='lightgray')
64
+ ```
65
+
66
+ ### Font sizes
67
+
68
+ Controlling the font sizes of a plot is very common so HoloViews provides a convenient option to set the ``fontsize``. The ``fontsize`` accepts a dictionary which allows supplying fontsizes for different components of the plot from the title, to the axis labels, ticks and legends. The full list of plot components that can be customized separately include:
69
+
70
+ ['xlabel', 'ylabel', 'zlabel', 'labels', 'xticks', 'yticks', 'zticks', 'ticks', 'minor_xticks', 'minor_yticks', 'minor_ticks', 'title', 'legend', 'legend_title']
71
+
72
+ Let's take a simple example customizing the title, the axis labels and the x/y-ticks separately:
73
+
74
+
75
+ ```python
76
+ hv.Curve([1, 2, 3], label='Title').opts(fontsize={'title': 16, 'labels': 14, 'xticks': 6, 'yticks': 12})
77
+ ```
78
+
79
+ ### Font scaling
80
+
81
+ Instead of control each property individually it is often useful to scale all fonts by a constant factor, e.g. to produce a more legible plot for presentations and posters. The `fontscale` option will affect the title, axis labels, tick labels, and legend:
82
+
83
+
84
+ ```python
85
+ (hv.Curve([1, 2, 3], label='A') * hv.Curve([3, 2, 1], label='B')).opts(fontscale=2, width=500, height=400, title='Title')
86
+ ```
87
+
88
+ ### Legend customization
89
+
90
+ When overlaying plots with different labels, a legend automatically appears to differentiate elements in the overlay. This legend can be customized in several ways:
91
+
92
+ - by **position**
93
+ - by adjusting the legend location within the figure using the `legend_position` option (e.g. `legend_position='bottom_right'`)
94
+ - by adjusting the legend location *outside* of the figure using the `legend_position` and `legend_offset` parameters (which then positions the legend in *screen* space) (e.g. `legend_position='right', legend_offset=(0, 200)`). **Note**: the `legend_position` option applies to `bokeh` and `matplotlib` backends but the `legend_offset` only applies to `bokeh`.
95
+ - by **style**
96
+ - by muting elements with `legend_muted=True` (applies only to the `bokeh` backend)
97
+ - by putting the legend elements in a column layout with `legend_cols=True` or (`legend_cols=int` in matplotlib)
98
+
99
+ These customizations are demonstrated by the examples that follow.
100
+
101
+ Moving the legend to the bottom right:
102
+
103
+
104
+ ```python
105
+ overlay = (hv.Curve([1, 2, 3], label='A') * hv.Curve([3, 2, 1], label='B')).opts(width=500, height=400)
106
+ overlay.opts(legend_position='bottom_right')
107
+ ```
108
+
109
+ Moving the legend outside, to the right of the plot:
110
+
111
+
112
+ ```python
113
+ overlay.opts(legend_position='right')
114
+ ```
115
+
116
+ Moving the legend outside, to the right of the plot but offset it 200 pixels higher:
117
+
118
+
119
+ ```python
120
+ overlay.opts(width=500, height=400, legend_position='right', legend_offset=(0, 200))
121
+ ```
122
+
123
+ Muting the legend and laying the labels out as columns.
124
+
125
+
126
+ ```python
127
+ overlay.opts(legend_muted=True, legend_cols=2)
128
+ ```
129
+
130
+ ### Plot hooks
131
+
132
+ HoloViews does not expose every single option a plotting extension like matplotlib or bokeh provides, therefore it is sometimes necessary to dig deeper to achieve precisely the customizations one might need. One convenient way of doing so is to use plot hooks to modify the plot object directly. The hooks are applied after HoloViews is done with the plot, allowing for detailed manipulations of the backend specific plot object.
133
+
134
+ The signature of a hook has two arguments, the HoloViews `plot` object that is rendering the plot and the `element` being rendered. From there the hook can modify the objects in the plot's handles, which provides convenient access to various components of a plot or simply access the ``plot.state`` which corresponds to the plot as a whole, e.g. in this case we define colors for the x- and y-labels of the plot.
135
+
136
+
137
+ ```python
138
+ def hook(plot, element):
139
+ print('plot.state: ', plot.state)
140
+ print('plot.handles: ', sorted(plot.handles.keys()))
141
+ plot.handles['xaxis'].axis_label_text_color = 'red'
142
+ plot.handles['yaxis'].axis_label_text_color = 'blue'
143
+
144
+ hv.Curve([1, 2, 3]).opts(hooks=[hook])
145
+ ```
146
+
147
+ ## Customizing axes
148
+
149
+ Controlling the axis scales is one of the most common changes to make to a plot, so we will provide a quick overview of the four main types of axes and then go into some more detail on how to control the axis labels, ranges, ticks and orientation.
150
+
151
+ ### Types of axes
152
+
153
+ There are four main types of axes supported across plotting backends, standard linear axes, log axes, datetime axes and categorical axes. In most cases HoloViews automatically detects the appropriate axis type to use based on the type of the data, e.g. numeric values use linear/log axes, date(time) values use datetime axes and string or other object types use categorical axes.
154
+
155
+ #### Linear axes
156
+
157
+ A linear axes is simply the default, as long as the data is numeric HoloViews will automatically use a linear axis on the plot.
158
+
159
+ #### Log axes
160
+
161
+ When the data is exponential it is often useful to use log axes, which can be enabled using independent ``logx`` and ``logy`` options. This way both semi-log and log-log plots can be achieved:
162
+
163
+
164
+ ```python
165
+ semilogy = hv.Curve(np.logspace(0, 5), label='Semi-log y axes')
166
+ loglog = hv.Curve((np.logspace(0, 5), np.logspace(0, 5)), label='Log-log axes')
167
+
168
+ semilogy.opts(logy=True) + loglog.opts(logx=True, logy=True, shared_axes=False)
169
+ ```
170
+
171
+ #### Datetime axes
172
+
173
+ All current plotting extensions allow plotting datetime data, if you ensure the dates array is of a valid datetime dtype.
174
+
175
+
176
+ ```python
177
+ from bokeh.sampledata.stocks import GOOG, AAPL
178
+
179
+ goog_dates = np.array(GOOG['date'], dtype=np.datetime64)
180
+ aapl_dates = np.array(AAPL['date'], dtype=np.datetime64)
181
+
182
+ goog = hv.Curve((goog_dates, GOOG['adj_close']), 'Date', 'Stock Index', label='Google')
183
+ aapl = hv.Curve((aapl_dates, AAPL['adj_close']), 'Date', 'Stock Index', label='Apple')
184
+
185
+ (goog * aapl).opts(width=600, legend_position='top_left')
186
+ ```
187
+
188
+ #### Categorical axes
189
+
190
+ While the handling of categorical data handles significantly between plotting extensions the same basic concepts apply. If the data is a string type or other object type it is formatted as a string and each unique category is assigned a tick along the axis. When overlaying elements the categories are combined and overlaid appropriately.
191
+
192
+ Whether an axis is categorical also depends on the Element type, e.g. a ``HeatMap`` always has two categorical axes while a ``Bars`` element always has a categorical x-axis. As a simple example let us create a set of points with categories along the x- and y-axes and render them on top of a `HeatMap` of th same data:
193
+
194
+
195
+ ```python
196
+ points = hv.Points([(chr(i+65), chr(j+65), i*j) for i in range(10) for j in range(10)], vdims='z')
197
+
198
+ heatmap = hv.HeatMap(points)
199
+
200
+ (heatmap * points).opts(
201
+ opts.HeatMap(toolbar='above', tools=['hover']),
202
+ opts.Points(tools=['hover'], size=dim('z')*0.3))
203
+ ```
204
+
205
+ As a more complex example which does not implicitly assume categorical axes due to the element type we will create a set of random samples indexed by categories from 'A' to 'E' using the ``Scatter`` Element and overlay them. Secondly we compute the mean and standard deviation for each category displayed using a set of ``ErrorBars`` and finally we overlay these two elements with a ``Curve`` representing the mean value . All these Elements respect the categorical index, providing us a view of the distribution of values in each category:
206
+
207
+
208
+ ```python
209
+ overlay = hv.NdOverlay({group: hv.Scatter(([group]*100, np.random.randn(100)*(5-i)-i))
210
+ for i, group in enumerate(['A', 'B', 'C', 'D', 'E'])})
211
+
212
+ errorbars = hv.ErrorBars([(k, el.reduce(function=np.mean), el.reduce(function=np.std))
213
+ for k, el in overlay.items()])
214
+
215
+ curve = hv.Curve(errorbars)
216
+
217
+ (errorbars * overlay * curve).opts(
218
+ opts.ErrorBars(line_width=5), opts.Scatter(jitter=0.2, alpha=0.5, size=6, height=400, width=600))
219
+ ```
220
+
221
+ Categorical axes are special in that they support multi-level nesting in some cases. Currently this is only supported for certain element types (BoxWhisker, Violin and Bars) but eventually all chart-like elements will interpret multiple key dimensions as a multi-level categorical hierarchy. To demonstrate this behavior consider the `BoxWhisker` plot below which support two-level nested categories:
222
+
223
+
224
+ ```python
225
+ groups = [chr(65+g) for g in np.random.randint(0, 3, 200)]
226
+ boxes = hv.BoxWhisker((groups, np.random.randint(0, 5, 200), np.random.randn(200)),
227
+ ['Group', 'Category'], 'Value').sort()
228
+
229
+ boxes.opts(width=600)
230
+ ```
231
+
232
+ ### Axis positions
233
+
234
+ Axes may be hidden or moved to a different location using the ``xaxis`` and ``yaxis`` options, which accept `None`, `'right'`/`'bottom'`, `'left'`/`'top'` and `'bare'` as values.
235
+
236
+
237
+ ```python
238
+ np.random.seed(42)
239
+ ys = np.random.randn(101).cumsum(axis=0)
240
+
241
+ curve = hv.Curve(ys, ('x', 'x-label'), ('y', 'y-label'))
242
+
243
+ (curve.relabel('No axis').opts(xaxis=None, yaxis=None) +
244
+ curve.relabel('Bare axis').opts(xaxis='bare') +
245
+ curve.relabel('Moved axis').opts(xaxis='top', yaxis='right'))
246
+ ```
247
+
248
+ ### Inverting axes
249
+
250
+ Another option to control axes is to invert the x-/y-axes using the ``invert_axes`` options, i.e. turn a vertical plot into a horizontal plot. Secondly each individual axis can be flipped left to right or upside down respectively using the ``invert_xaxis`` and ``invert_yaxis`` options.
251
+
252
+
253
+ ```python
254
+ bars = hv.Bars([('Australia', 10), ('United States', 14), ('United Kingdom', 7)], 'Country')
255
+
256
+ (bars.relabel('Invert axes').opts(invert_axes=True, width=400) +
257
+ bars.relabel('Invert x-axis').opts(invert_xaxis=True) +
258
+ bars.relabel('Invert y-axis').opts(invert_yaxis=True)).opts(shared_axes=False)
259
+ ```
260
+
261
+ ### Axis labels
262
+
263
+ Ordinarily axis labels are controlled using the dimension label, however explicitly ``xlabel`` and ``ylabel`` options make it possible to override the label at the plot level. Additionally the ``labelled`` option allows specifying which axes should be labelled at all, making it possible to hide axis labels:
264
+
265
+
266
+ ```python
267
+ (curve.relabel('Dimension labels') +
268
+ curve.relabel("xlabel='Custom x-label'").opts(xlabel='Custom x-label') +
269
+ curve.relabel('Unlabelled').opts(labelled=[]))
270
+ ```
271
+
272
+ ### Axis ranges
273
+
274
+ The ranges of a plot are ordinarily controlled by computing the data range and combining it with the dimension ``range`` and ``soft_range`` but they may also be padded or explicitly overridden using ``xlim`` and ``ylim`` options.
275
+
276
+ #### Dimension ranges
277
+
278
+ * **data range**: The data range is computed by min and max of the dimension values
279
+ * **range**: Hard override of the data range
280
+ * **soft_range**: Soft override of the data range
281
+
282
+ ##### **Dimension.range**
283
+
284
+ Setting the ``range`` of a Dimension overrides the data ranges, i.e. here we can see that despite the fact the data extends to x=100 the axis is cut off at 90:
285
+
286
+
287
+ ```python
288
+ curve.redim(x=hv.Dimension('x', range=(-10, 90)))
289
+ ```
290
+
291
+ ##### Dimension.soft_range
292
+
293
+ Declaringa ``soft_range`` on the other hand combines the data range and the supplied range, i.e. it will pick whichever extent is wider. Using the same example as above we can see it uses the -10 value supplied in the soft_range but also extends to 100, which is the upper bound of the actual data:
294
+
295
+
296
+ ```python
297
+ curve.redim(x=hv.Dimension('x', soft_range=(-10, 90)))
298
+ ```
299
+
300
+ #### Padding
301
+
302
+ Applying padding to the ranges is an easy way to ensure that the data is not obscured by the margins. The padding is specified by the fraction by which to increase auto-ranged extents to make datapoints more visible around borders. The default for most elements is `padding=0.1`. The padding considers the width and height of the plot to keep the visual extent of the padding equal. The padding values can be specified with three levels of detail:
303
+
304
+ * 1. A single numeric value (e.g. ``padding=0.1``)
305
+ * 2. A tuple specifying the padding for the x/y(/z) axes respectively (e.g. ``padding=(0, 0.1)``)
306
+ * 3. A tuple of tuples specifying padding for the lower and upper bound respectively (e.g. ``padding=(0, (0, 0.1))``)
307
+
308
+
309
+ ```python
310
+ (curve.relabel('Pad both axes').opts(padding=0.1) +
311
+ curve.relabel('Pad y-axis').opts(padding=(0, 0.1)) +
312
+ curve.relabel('Pad y-axis upper bound').opts(padding=(0, (0, 0.1)))).opts(shared_axes=False)
313
+ ```
314
+
315
+ #### xlim/ylim
316
+
317
+ The data ranges, dimension ranges and padding combine across plots in an overlay to ensure that all the data is contained in the viewport. In some cases it is more convenient to override the ranges with explicit ``xlim`` and ``ylim`` options which have the highest precedence and will be respected no matter what.
318
+
319
+
320
+ ```python
321
+ curve.relabel('Explicit xlim/ylim').opts(xlim=(-10, 110), ylim=(-14, 6))
322
+ ```
323
+
324
+ #### Autoranging
325
+
326
+ With the `autorange` keyword, you can ensure the data in the viewport is automatically ranged to maximise the use of the x- or y-axis. To illustrate, here is the same `curve` autoranging on the `y-axis`: note the difference in behavior when zooming into the data:
327
+
328
+
329
+ ```python
330
+ curve.relabel('Autoranging on y').opts(autorange='y')
331
+ ```
332
+
333
+ To pin the ends of the ranges you can use the `xlim` and `ylim` options, using a value of `None` to allow autoranging to operate. Here the bottom range of the y-axis is pinned to the value of `-14`:
334
+
335
+
336
+ ```python
337
+ curve.relabel('Autoranging on y with set lower limit').opts(autorange='y', ylim=(-14,None))
338
+ ```
339
+
340
+ Autoranging works analogously for the x-axis and also respects the padding setting. In addition, autoranging is triggered when the plotted data is updated dynamically, as is common when building interactive visualizations with operations or `DynamicMap`s.
341
+
342
+ ### Axis ticks
343
+
344
+ Setting tick locations differs a little bit depending on the plotting extension, interactive backends such as bokeh or plotly dynamically update the ticks, which means fixed tick locations may not be appropriate and the formatters have to be applied in Javascript code. Nevertheless most options to control the ticking are consistent across extensions.
345
+
346
+ #### Tick locations
347
+
348
+ The number and locations of ticks can be set in three main ways:
349
+
350
+ * Number of ticks: Declare the number of desired ticks as an integer
351
+ * List of tick positions: An explicit list defining the list of positions at which to draw a tick
352
+ * List of tick positions and labels: A list of tuples of the form (position, label)
353
+
354
+
355
+ ```python
356
+ (curve.relabel('N ticks (xticks=10)').opts(xticks=10) +
357
+ curve.relabel('Listed ticks (xticks=[0, 1, 2])').opts(xticks=[0, 50, 100]) +
358
+ curve.relabel("Tick labels (xticks=[(0, 'zero'), ...").opts(xticks=[(0, 'zero'), (50, 'fifty'), (100, 'one hundred')]))
359
+ ```
360
+
361
+ Lastly each extension will accept the custom Ticker objects the library provides, which can be used to achieve layouts not usually available.
362
+
363
+ #### Tick formatters
364
+
365
+ Tick formatting works very differently in different backends, however the ``xformatter`` and ``yformatter`` options try to minimize these differences. Tick formatters may be defined in one of three formats:
366
+
367
+ * A classic format string such as ``'%d'``, ``'%.3f'`` or ``'%d'`` which may also contain other characters (``'$%.2f'``)
368
+ * A ``bokeh.models.TickFormatter`` in bokeh and a ``matplotlib.ticker.Formatter`` instance in matplotlib
369
+
370
+ Here is a small example demonstrating how to use the string approaches:
371
+
372
+
373
+ ```python
374
+ curve.relabel('Tick formatters').opts(xformatter='%.0f days', yformatter='$%.2f', width=500)
375
+ ```
376
+
377
+ #### Tick orientation
378
+
379
+ Particularly when dealing with categorical axes it is often useful to control the tick rotation. This can be achieved using the ``xrotation`` and ``yrotation`` options which accept angles in degrees.
380
+
381
+
382
+ ```python
383
+ bars.opts(xrotation=45)
384
+ ```
385
+
386
+ ### Twin axes
387
+ *(Available in HoloViews >= 1.17, requires Bokeh >=3.2)*
388
+
389
+ HoloViews now supports displaying overlays containing two different value dimensions as twin axes for chart elements. To maintain backwards compatibility, this feature is only enabled by setting the `multi_y=True` option on the overlay.
390
+
391
+ To illustrate, here is an overlay containing three curves with two value dimensions ('A' and 'B'). Setting `multi_y=True` then maps these two value dimensions to twin-axes:
392
+
393
+
394
+ ```python
395
+ overlay = hv.Curve([1, 2, 3], vdims=['A']) * hv.Curve([2, 3, 4], vdims=['A']) * hv.Curve([3, 2, 1], vdims=['B'])
396
+ overlay.opts(multi_y=True)
397
+ ```
398
+
399
+ Additional value dimensions do map to additional axes but be aware that support of multi axes beyond twin axes is currently considered experimental.
400
+
401
+ The first value dimension is mapped to the left-hand axis and the second value dimension maps to the right axis. Note that the two axes are individually zoomable by hovering over them and using the Bokeh wheelzoom tool.
402
+
403
+
404
+ #### Supported `multi_y` options
405
+
406
+ When `multi_y` is enabled, you can set individual axis options on the elements of the overlay.
407
+
408
+ In this example, the left axis uses the default options while the right axis is an inverted, autoranged, log axis with a set `ylim`:
409
+
410
+
411
+ ```python
412
+ (hv.Curve([1, 2, 3], vdims=['A'])
413
+ * hv.Curve([2, 3, 4], vdims=['B']).opts(autorange='y', invert_yaxis=True, logy=True, ylim=(1,10),
414
+ ylabel='B custom', fontsize={'ylabel':10})
415
+ ).opts(multi_y=True)
416
+ ```
417
+
418
+ Supported options for customizing individual axes are `apply_ranges`, `autorange='y'`, `invert_yaxis`, `logy` and `ylim`, `yaxis` as well as the following options for labelling: `labelled`, `ylabel` and the `'ylabel'` setting in `fontsize`.
419
+
420
+ Note that as of HoloViews 1.17.0, `multi_y` does not have streaming plot support, extra axis labels are not dynamic and only the `RangeXY` linked stream is aware of additional y-axes.
421
+
422
+ ### Subcoordinate y-axis
423
+ *(Available in HoloViews >= 1.18)*
424
+
425
+ HoloViews enables you to create overlays where each element has its own distinct y-axis subcoordinate system. To activate this feature, set the `subcoordinate_y` keyword to True for **each** overlay element; the default is False. When using `subcoordinate_y=True`, setting a `label` for each element is required for proper rendering and identification.This will automatically distribute overlay elements along the y-axis.
426
+
427
+ For more fine-grained control over y-axis positioning, you can specify a numerical 2-tuple for subcoordinate_y with values ranging from 0 to 1. Additionally, the `subcoordinate_scale` keyword, which defaults to 1, allows you to adjust the vertical scale of each element. This option is only applicable when `subcoordinate_y=True`. For example, setting a single Curve's `subcoordinate_scale` to 2 will result in it overlapping 50% with its adjacent elements.
428
+
429
+
430
+ ```python
431
+ x = np.linspace(0, 10*np.pi)
432
+
433
+ curves = [
434
+ hv.Curve((x + i*np.pi/2, np.sin(x)), label=f'Line {i}').opts(subcoordinate_y=True, subcoordinate_scale=1.2)
435
+ for i in range(3)
436
+ ]
437
+
438
+ hv.Overlay(curves).opts(show_legend=False)
439
+ ```
hvplot_docs/Deploying_Bokeh_Apps.md ADDED
@@ -0,0 +1,553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deploying Bokeh Apps
2
+
3
+
4
+ ```python
5
+ import numpy as np
6
+ import holoviews as hv
7
+ hv.extension('bokeh')
8
+ ```
9
+
10
+ ## Purpose
11
+
12
+ HoloViews is an incredibly convenient way of working interactively and exploratively within a notebook or commandline context. However, once you have implemented a polished interactive dashboard or some other complex interactive visualization, you will often want to deploy it outside the notebook to share with others who may not be comfortable with the notebook interface.
13
+
14
+ In the simplest case, to visualize some HoloViews container or element `obj`, you can export it to a standalone HTML file for sharing using the `save` function of the Bokeh renderer:
15
+
16
+ ```
17
+ hv.save(obj, 'out.html')
18
+ ```
19
+
20
+ This command will generate a file `out.html` that you can put on any web server, email directly to colleagues, etc.; it is fully self-contained and does not require any Python server to be installed or running.
21
+
22
+ Unfortunately, a static approach like this cannot support any HoloViews object that uses DynamicMap (either directly or via operations that return DynamicMaps like `decimate`, `datashade`, and `rasterize`). Anything with DynamicMap requires a live, running Python server to dynamically select and provide the data for the various parameters that can be selected by the user. Luckily, when you need a live Python process during the visualization, the [Bokeh server](http://bokeh.pydata.org/en/latest/docs/user_guide/server.html) provides a very convenient way of deploying HoloViews plots and interactive dashboards in a scalable and flexible manner. The Bokeh server allows all the usual interactions that HoloViews lets you define and more including:
23
+
24
+ * responding to plot events and tool interactions via [Linked Streams](./13-Custom_Interactivity.ipynb)
25
+ * generating and interacting with plots via the usual widgets that HoloViews supports for HoloMap and DynamicMap objects.
26
+ * using periodic and timeout events to drive plot updates
27
+ * combining HoloViews plots with custom Bokeh plots to quickly write highly customized apps.
28
+
29
+ ## Overview
30
+
31
+ In this guide we will cover how we can deploy a Bokeh app from a HoloViews plot in a number of different ways:
32
+
33
+ 1. Inline from within the Jupyter notebook
34
+
35
+ 2. Starting a server interactively and open it in a new browser window.
36
+
37
+ 3. From a standalone script file
38
+
39
+ 4. Combining HoloViews and Bokeh models to create a more customized app
40
+
41
+ If you have read a bit about HoloViews you will know that HoloViews objects are not themselves plots, instead they contain sufficient data and metadata allowing them to be rendered automatically in a notebook context. In other words, when a HoloViews object is evaluated a backend specific ``Renderer`` converts the HoloViews object into Bokeh models, a Matplotlib figure or a Plotly graph. This intermediate representation is then rendered as an image or as HTML with associated Javascript, which is what ends up being displayed.
42
+
43
+ ## The workflow
44
+
45
+ The most convenient way to work with HoloViews is to iteratively improve a visualization in the notebook. Once you have developed a visualization or dashboard that you would like to deploy you can use the ``BokehRenderer`` to export the visualization as illustrated above, or you can deploy it as a Bokeh server app.
46
+
47
+ Here we will create a small interactive plot, using [Linked Streams](./13-Custom_Interactivity.ipynb), which mirrors the points selected using box- and lasso-select tools in a second plot and computes some statistics:
48
+
49
+
50
+ ```python
51
+ # Declare some points
52
+ points = hv.Points(np.random.randn(1000,2 ))
53
+
54
+ # Declare points as source of selection stream
55
+ selection = hv.streams.Selection1D(source=points)
56
+
57
+ # Write function that uses the selection indices to slice points and compute stats
58
+ def selected_info(index):
59
+ arr = points.array()[index]
60
+ if index:
61
+ label = 'Mean x, y: %.3f, %.3f' % tuple(arr.mean(axis=0))
62
+ else:
63
+ label = 'No selection'
64
+ return points.clone(arr, label=label).opts(color='red')
65
+
66
+ # Combine points and DynamicMap
67
+ selected_points = hv.DynamicMap(selected_info, streams=[selection])
68
+ layout = points.opts(tools=['box_select', 'lasso_select']) + selected_points
69
+
70
+ layout
71
+ ```
72
+
73
+ <img src='https://assets.holoviews.org/gifs/examples/streams/bokeh/point_selection1d.gif'></img>
74
+
75
+ #### Working with the BokehRenderer
76
+
77
+ When working with Bokeh server or wanting to manipulate a backend specific plot object you will have to use a HoloViews ``Renderer`` directly to convert the HoloViews object into the backend specific representation. Therefore we will start by getting a hold of a ``BokehRenderer``:
78
+
79
+
80
+ ```python
81
+ renderer = hv.renderer('bokeh')
82
+ print(renderer)
83
+ ```
84
+
85
+ ```python
86
+ BokehRenderer()
87
+ ```
88
+
89
+ All ``Renderer`` classes in HoloViews are so called ParameterizedFunctions; they provide both classmethods and instance methods to render an object. You can easily create a new ``Renderer`` instance using the ``.instance`` method:
90
+
91
+
92
+ ```python
93
+ renderer = renderer.instance(mode='server')
94
+ ```
95
+
96
+ Renderers can also have different modes. In this case we will instantiate the renderer in ``'server'`` mode, which tells the Renderer to render the HoloViews object to a format that can easily be deployed as a server app. Before going into more detail about deploying server apps we will quickly remind ourselves how the renderer turns HoloViews objects into Bokeh models.
97
+
98
+ ### Figures
99
+
100
+ The BokehRenderer converts the HoloViews object to a HoloViews ``Plot``, which holds the Bokeh models that will be rendered to screen. As a very simple example we can convert a HoloViews ``Image`` to a HoloViews plot:
101
+
102
+
103
+ ```python
104
+ plot = renderer.get_plot(layout)
105
+ print(plot)
106
+ ```
107
+
108
+ ```
109
+ <LayoutPlot LayoutPlot01811>
110
+ ```
111
+
112
+ Using the ``state`` attribute on the HoloViews plot we can access the Bokeh ``Column`` model, which we can then work with directly.
113
+
114
+
115
+ ```python
116
+ plot.state
117
+ ```
118
+
119
+ **Column**(id='1570', ...)
120
+
121
+ In the background this is how HoloViews converts any HoloViews object into Bokeh models, which can then be converted to embeddable or standalone HTML and be rendered in the browser. This conversion is usually done in the background using the ``figure_data`` method:
122
+
123
+
124
+ ```python
125
+ html = renderer._figure_data(plot, 'html')
126
+ ```
127
+
128
+ ### Bokeh Documents
129
+
130
+ In Bokeh the [``Document``](http://bokeh.pydata.org/en/latest/docs/reference/document.html) is the basic unit at which Bokeh models (such as plots, layouts and widgets) are held and serialized. The serialized JSON representation is then sent to BokehJS on the client-side browser. When in ``'server'`` mode the BokehRenderer will automatically return a server Document:
131
+
132
+
133
+ ```python
134
+ renderer(layout)
135
+ ```
136
+
137
+ ```
138
+ (<bokeh.document.Document at 0x11afc7590>,
139
+ {'file-ext': 'html', 'mime_type': u'text/html'})
140
+ ```
141
+
142
+ We can also easily use the ``server_doc`` method to get a Bokeh ``Document``, which does not require you to make an instance in 'server' mode.
143
+
144
+
145
+ ```python
146
+ doc = renderer.server_doc(layout)
147
+ doc.title = 'HoloViews App'
148
+ ```
149
+
150
+ In the background however, HoloViews uses the Panel library to render components to a Bokeh model which can be rendered in the notebook, to a file or on a server:
151
+
152
+
153
+ ```python
154
+ import panel as pn
155
+
156
+ model = pn.panel(layout).get_root()
157
+ model
158
+ ```
159
+
160
+ For more information on the interaction between Panel and HoloViews see the the [Panel documentation](https://panel.holoviz.org/reference/panes/HoloViews.html).
161
+
162
+ ## Deploying with ``panel serve``
163
+
164
+ Deployment from a script with `panel serve` is one of the most common ways to deploy a Bokeh app. Any ``.py`` or ``.ipynb`` file that attaches a plot to Bokeh's ``curdoc`` can be deployed using ``panel serve``. The easiest way to do this is using wrapping the HoloViews component in Panel using ``pn.panel(hvobj)`` and then calling the ``panel_obj.servable()`` method, which accepts any HoloViews object ensures that the plot is discoverable by Panel and the underlying Bokeh server. See below to see a full standalone script:
165
+
166
+ ```python
167
+ import numpy as np
168
+ import panel as pn
169
+ import holoviews as hv
170
+ import holoviews.plotting.bokeh
171
+
172
+ points = hv.Points(np.random.randn(1000,2 )).opts(tools=['box_select', 'lasso_select'])
173
+ selection = hv.streams.Selection1D(source=points)
174
+
175
+ def selected_info(index):
176
+ arr = points.array()[index]
177
+ if index:
178
+ label = 'Mean x, y: %.3f, %.3f' % tuple(arr.mean(axis=0))
179
+ else:
180
+ label = 'No selection'
181
+ return points.clone(arr, label=label).opts(color='red')
182
+
183
+ layout = points + hv.DynamicMap(selected_info, streams=[selection])
184
+
185
+ pn.panel(layout).servable(title='HoloViews App')
186
+ ```
187
+
188
+ In just a few steps we can iteratively refine in the notebook to a deployable Panel app. Note also that we can also deploy an app directly from a notebook. By using `.servable()` in a notebook any regular ``.ipynb`` file can be made into a valid Panel/Bokeh app, which can be served with ``panel serve example.ipynb``.
189
+
190
+ It is also possible to create a Bokeh `Document` more directly working with the underlying Bokeh representation instead. This in itself is sufficient to make the plot servable using `bokeh serve`:
191
+
192
+
193
+ ```python
194
+ hv.renderer('bokeh').server_doc(layout)
195
+ ```
196
+
197
+ In addition to starting a server from a script we can also start up a server interactively, so let's do a quick deep dive into Bokeh ``Application`` and ``Server`` objects and how we can work with them from within HoloViews.
198
+
199
+ ## Bokeh Server
200
+
201
+ To start a Bokeh server directly from a notebook we can also use Panel, specifically we'll use the `panel.serve` function. We'll define a ``DynamicMap`` of a sine ``Curve`` varying by frequency, phase and an offset and then create a server instance using Panel:
202
+
203
+
204
+ ```python
205
+ def sine(frequency, phase, amplitude):
206
+ xs = np.linspace(0, np.pi*4)
207
+ return hv.Curve((xs, np.sin(frequency*xs+phase)*amplitude)).opts(width=800)
208
+
209
+ ranges = dict(frequency=(1, 5), phase=(-np.pi, np.pi), amplitude=(-2, 2), y=(-2, 2))
210
+ dmap = hv.DynamicMap(sine, kdims=['frequency', 'phase', 'amplitude']).redim.range(**ranges)
211
+
212
+ server = pn.serve(dmap, start=False, show=False)
213
+ ```
214
+
215
+ ```
216
+ <bokeh.server.server.Server object at 0x10b3a0510>
217
+ ```
218
+
219
+ Next we can define a callback on the IOLoop that will open the server app in a new browser window and actually start the app (and if outside the notebook the IOLoop):
220
+
221
+
222
+ ```python
223
+ server.start()
224
+ server.show('/')
225
+
226
+ # Outside the notebook ioloop needs to be started
227
+ # from tornado.ioloop import IOLoop
228
+ # loop = IOLoop.current()
229
+ # loop.start()
230
+ ```
231
+
232
+ After running the cell above you should have noticed a new browser window popping up displaying our plot. Once you are done playing with it you can stop it with:
233
+
234
+
235
+ ```python
236
+ server.stop()
237
+ ```
238
+
239
+ We can achieve the equivalent using the `.show` method on a Panel object:
240
+
241
+
242
+ ```python
243
+ server = pn.panel(dmap).show()
244
+ ```
245
+
246
+ <img width='80%' src="https://assets.holoviews.org/gifs/guides/user_guide/Deploying_Bokeh_Apps/bokeh_server_new_window.png"></img>
247
+
248
+ We will once again stop this Server before continuing:
249
+
250
+
251
+ ```python
252
+ server.stop()
253
+ ```
254
+
255
+ ## Inlining apps in the notebook
256
+
257
+ Instead of displaying our app in a new browser window we can also display an app inline in the notebook simply by using the `.app` method on Panel object. The server app will be killed whenever you rerun or delete the cell that contains the output. Additionally, if your Jupyter Notebook server is not running on the default address or port (``localhost:8888``) supply the websocket origin, which should match the first part of the URL of your notebook:
258
+
259
+
260
+ ```python
261
+ dmap
262
+ ```
263
+
264
+ <img width='80%' src='https://assets.holoviews.org/gifs/guides/user_guide/Deploying_Bokeh_Apps/bokeh_server_inline_simple.gif'></img>
265
+
266
+ ## Periodic callbacks
267
+
268
+ One of the most important features of deploying apps is the ability to attach asynchronous, periodic callbacks, which update the plot. The simplest way of achieving this is to attach a ``Counter`` stream on the plot which is incremented on each callback. As a simple demo we'll simply compute a phase offset from the counter value, animating the sine wave:
269
+
270
+
271
+ ```python
272
+ def sine(counter):
273
+ phase = counter*0.1%np.pi*2
274
+ xs = np.linspace(0, np.pi*4)
275
+ return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)
276
+
277
+ counter = hv.streams.Counter()
278
+ hv.DynamicMap(sine, streams=[counter])
279
+
280
+ dmap
281
+ ```
282
+
283
+ <img width='80%' src='https://assets.holoviews.org/gifs/guides/user_guide/Deploying_Bokeh_Apps/bokeh_server_periodic.gif'></img>
284
+
285
+ Once we have created a Panel object we can call the `add_periodic_callback` method to set up a periodic callback. The first argument to the method is the callback and the second argument period specified in milliseconds. As soon as we start this callback you should see the Curve above become animated.
286
+
287
+
288
+ ```python
289
+ def update():
290
+ counter.event(counter=counter.counter+1)
291
+
292
+ cb = pn.state.add_periodic_callback(update, period=200)
293
+ ```
294
+
295
+ Once started we can stop and start it at will using the `.stop` and `.start` methods:
296
+
297
+
298
+ ```python
299
+ cb.stop()
300
+ ```
301
+
302
+ ## Combining Bokeh Application and Flask Application
303
+
304
+ While Panel and Bokeh are great ways to create an application often we want to leverage the simplicity of a Flask server. With Flask we can easily embed a HoloViews, Bokeh and Panel application in a regular website. The main idea for getting Bokeh and Flask to work together is to run both apps on ports and then use Flask to pull the Bokeh Serve session with `pull_session` from [bokeh.client.session](https://bokeh.pydata.org/en/latest/docs/reference/client/session.html).
305
+
306
+
307
+ ```python
308
+ def sine(frequency, phase, amplitude):
309
+ xs = np.linspace(0, np.pi*4)
310
+ return hv.Curve((xs, np.sin(frequency*xs+phase)*amplitude)).options(width=800)
311
+
312
+ ranges = dict(frequency=(1, 5), phase=(-np.pi, np.pi), amplitude=(-2, 2), y=(-2, 2))
313
+ dmap = hv.DynamicMap(sine, kdims=['frequency', 'phase', 'amplitude']).redim.range(**ranges)
314
+
315
+ pn.serve(dmap, websocket_origin='localhost:5000', port=5006, show=False)
316
+ ```
317
+
318
+ We run load up our dynamic map into a Bokeh Application with the parameter `allow_websocket_origin=["localhost:5000"]`
319
+
320
+ ```python
321
+ from bokeh.client import pull_session
322
+ from bokeh.embed import server_session
323
+ from flask import Flask, render_template
324
+ from flask import send_from_directory
325
+
326
+ app = Flask(__name__)
327
+
328
+
329
+ # locally creates a page
330
+ @app.route('/')
331
+ def index():
332
+ with pull_session(url="http://localhost:5006/") as session:
333
+ # generate a script to load the customized session
334
+ script = server_session(session_id=session.id, url='http://localhost:5006')
335
+ # use the script in the rendered page
336
+ return render_template("embed.html", script=script, template="Flask")
337
+
338
+ if __name__ == '__main__':
339
+ # runs app in debug mode
340
+ app.run(port=5000, debug=True)
341
+ ```
342
+
343
+ Note that in a notebook context we cannot use `pull_session` but this example demonstrates how we can embed the Bokeh server inside a simple flask app.
344
+
345
+ This is an example of a basic flask app. To find out more about Flask a tutorial can be found on the [Flask Quickstart Guide](http://flask.pocoo.org/docs/1.0/quickstart/#).
346
+
347
+
348
+
349
+ Below is an example of a basic Flask App that pulls from the Bokeh Application. The Bokeh Application is using `Server` from Bokeh and `IOLoop` from tornado to run the app.
350
+
351
+ ```python
352
+ # holoviews.py
353
+
354
+ import holoviews as hv
355
+ import panel as pn
356
+ import numpy as np
357
+
358
+ hv.extension('bokeh')
359
+
360
+ def sine(frequency, phase, amplitude):
361
+ xs = np.linspace(0, np.pi*4)
362
+ return hv.Curve((xs, np.sin(frequency*xs+phase)*amplitude)).options(width=800)
363
+
364
+ if __name__ == '__main__':
365
+ ranges = dict(frequency=(1, 5), phase=(-np.pi, np.pi), amplitude=(-2, 2), y=(-2, 2))
366
+ dmap = hv.DynamicMap(sine, kdims=['frequency', 'phase', 'amplitude']).redim.range(**ranges)
367
+ pn.serve(dmap, port=5006, allow_websocket_origin=["localhost:5000"], show=False)
368
+ ```
369
+
370
+ ```python
371
+ #flaskApp.py
372
+
373
+ from bokeh.client import pull_session
374
+ from bokeh.embed import server_session
375
+ from flask import Flask, render_template
376
+ from flask import send_from_directory
377
+
378
+ app = Flask(__name__)
379
+
380
+ # locally creates a page
381
+ @app.route('/')
382
+ def index():
383
+ with pull_session(url="http://localhost:5006/") as session:
384
+ # generate a script to load the customized session
385
+ script = server_session(session_id=session.id, url='http://localhost:5006')
386
+ # use the script in the rendered page
387
+ return render_template("embed.html", script=script, template="Flask")
388
+
389
+
390
+ if __name__ == '__main__':
391
+ # runs app in debug mode
392
+ app.run(port=5000, debug=True)
393
+ ```
394
+
395
+ ```html
396
+ <!-- embed.html -->
397
+
398
+ <!doctype html>
399
+
400
+ <html lang="en">
401
+ <head>
402
+ <meta charset="utf-8">
403
+ <title>Embedding a Bokeh Server With Flask</title>
404
+ </head>
405
+
406
+ <body>
407
+ <div>
408
+ This Bokeh app below served by a Bokeh server that has been embedded
409
+ in another web app framework. For more information see the section
410
+ <a target="_blank" href="https://bokeh.pydata.org/en/latest/docs/user_guide/server.html#embedding-bokeh-server-as-a-library">Embedding Bokeh Server as a Library</a>
411
+ in the User's Guide.
412
+ </div>
413
+ {{ script|safe }}
414
+ </body>
415
+ </html>
416
+ ```
417
+
418
+ If you wish to replicate navigate to the `examples/gallery/apps/flask` directory and follow the these steps:
419
+
420
+ * Step One: call `python holoviews_app.py` in the terminal (this will start the Panel/Bokeh server)
421
+ * Step Two: open a new terminal and call `python flask_app.py` (this will start the Flask application)
422
+ * Step Three: go to web browser and type `localhost:5000` and the app will appear
423
+
424
+ ## Combining HoloViews and Panel or Bokeh Plots/Widgets
425
+
426
+ While HoloViews provides very convenient ways of creating an app it is not as fully featured as Bokeh itself is. Therefore we often want to extend a HoloViews based app with Panel or Bokeh plots and widgets. Here we will discover to achieve this with both Panel and then the equivalent using pure Bokeh.
427
+
428
+
429
+ ```python
430
+ import holoviews as hv
431
+ import numpy as np
432
+ import panel as pn
433
+
434
+ # Create the holoviews app again
435
+ def sine(phase):
436
+ xs = np.linspace(0, np.pi*4)
437
+ return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)
438
+
439
+ stream = hv.streams.Stream.define('Phase', phase=0.)()
440
+ dmap = hv.DynamicMap(sine, streams=[stream])
441
+
442
+ start, end = 0, np.pi*2
443
+ slider = pn.widgets.FloatSlider(start=start, end=end, value=start, step=0.2, name="Phase")
444
+
445
+ # Create a slider and play buttons
446
+ def animate_update():
447
+ year = slider.value + 0.2
448
+ if year > end:
449
+ year = start
450
+ slider.value = year
451
+
452
+ def slider_update(event):
453
+ # Notify the HoloViews stream of the slider update
454
+ stream.event(phase=event.new)
455
+
456
+ slider.param.watch(slider_update, 'value')
457
+
458
+ def animate(event):
459
+ if button.name == '► Play':
460
+ button.name = '❚❚ Pause'
461
+ callback.start()
462
+ else:
463
+ button.name = '► Play'
464
+ callback.stop()
465
+
466
+ button = pn.widgets.Button(name='► Play', width=60, align='end')
467
+ button.on_click(animate)
468
+ callback = pn.state.add_periodic_callback(animate_update, 50, start=False)
469
+
470
+ app = pn.Column(
471
+ dmap,
472
+ pn.Row(slider, button)
473
+ )
474
+
475
+ app
476
+ ```
477
+
478
+ If instead we want to deploy this we could add `.servable` as discussed before or use `pn.serve`. Note however that when using `pn.serve` all sessions will share the same state therefore it is best to
479
+ wrap the creation of the app in a function which we can then provide to `pn.serve`. For more detail on deploying Panel applications also see the [Panel server deployment guide](https://panel.holoviz.org/user_guide/Server_Deployment.html).
480
+
481
+ Now we can reimplement the same example using Bokeh allowing us to compare and contrast the approaches:
482
+
483
+
484
+ ```python
485
+ import numpy as np
486
+ import holoviews as hv
487
+
488
+ from bokeh.io import show, curdoc
489
+ from bokeh.layouts import layout
490
+ from bokeh.models import Slider, Button
491
+
492
+ renderer = hv.renderer('bokeh').instance(mode='server')
493
+
494
+ # Create the holoviews app again
495
+ def sine(phase):
496
+ xs = np.linspace(0, np.pi*4)
497
+ return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)
498
+
499
+ stream = hv.streams.Stream.define('Phase', phase=0.)()
500
+ dmap = hv.DynamicMap(sine, streams=[stream])
501
+
502
+ # Define valid function for FunctionHandler
503
+ # when deploying as script, simply attach to curdoc
504
+ def modify_doc(doc):
505
+ # Create HoloViews plot and attach the document
506
+ hvplot = renderer.get_plot(dmap, doc)
507
+
508
+ # Create a slider and play buttons
509
+ def animate_update():
510
+ year = slider.value + 0.2
511
+ if year > end:
512
+ year = start
513
+ slider.value = year
514
+
515
+ def slider_update(attrname, old, new):
516
+ # Notify the HoloViews stream of the slider update
517
+ stream.event(phase=new)
518
+
519
+ start, end = 0, np.pi*2
520
+ slider = Slider(start=start, end=end, value=start, step=0.2, title="Phase")
521
+ slider.on_change('value', slider_update)
522
+
523
+ callback_id = None
524
+
525
+ def animate():
526
+ global callback_id
527
+ if button.label == '► Play':
528
+ button.label = '❚❚ Pause'
529
+ callback_id = doc.add_periodic_callback(animate_update, 50)
530
+ else:
531
+ button.label = '► Play'
532
+ doc.remove_periodic_callback(callback_id)
533
+ button = Button(label='► Play', width=60)
534
+ button.on_click(animate)
535
+
536
+ # Combine the holoviews plot and widgets in a layout
537
+ plot = layout([
538
+ [hvplot.state],
539
+ [slider, button]], sizing_mode='fixed')
540
+
541
+ doc.add_root(plot)
542
+ return doc
543
+
544
+ # To display in the notebook
545
+ show(modify_doc, notebook_url='localhost:8888')
546
+
547
+ # To display in a script
548
+ # doc = modify_doc(curdoc())
549
+ ```
550
+
551
+ <img width='80%' src='https://assets.holoviews.org/gifs/guides/user_guide/Deploying_Bokeh_Apps/bokeh_server_play.gif'></img>
552
+
553
+ As you can see depending on your needs you have complete freedom whether to use just HoloViews and deploy your application, combine it Panel or even with pure Bokeh.
hvplot_docs/Explorer.md ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import hvplot.pandas # noqa
3
+ import xarray as xr
4
+ ```
5
+
6
+ hvPlot API provides a simple and intuitive way to create plots. However when you are exploring data you don't always know in advance the best way to display it, or even what kind of plot would be best to visualize the data. You will very likely embark in an iterative process that implies choosing a kind of plot, setting various options, running some code, and repeat until you're satisfied with the output and the insights you get. The *Explorer* is a *Graphical User Interface* that allows you to easily generate customized plots, which in practice gives you the possibility to **explore** both your data and hvPlot's extensive API.
7
+
8
+ :::{note}
9
+ The *Explorer* has been added to hvPlot in version <code>0.8.0</code> and improve over the next versions, in particular version <code>0.9.0</code> added support to Xarray input types. We plan to keep on improving the explorer, making it a powerful exploration app, in the meantime please report any issue or feature request <a href='https://github.com/holoviz/hvplot/'>on GitHub</a>.
10
+ :::
11
+
12
+ ## Set up
13
+
14
+ For an explorer to be displayed in a notebook you need to load the hvPlot extension, which happens automatically when you execute an import like `import hvplot.pandas`. You could also just run `hvplot.extension('bokeh')`. If instead of building Bokeh plots you would rather build Matplotlib or Plotly plots, simply execute once `hvplot.extension('matplotlib')` or `hvplot.extension('matplotlib')` before displaying the explorer.
15
+
16
+ ## Instantiate
17
+
18
+ An explorer can be instantiated in two different ways:
19
+
20
+ - via the top-level `explorer()` function: `from hvplot import explorer; explorer(data)`
21
+ - via the `.explorer()` method available on the `.hvplot` namespace: `data.hvplot.explorer()` (added in version 0.9.0)
22
+
23
+ The `explorer` callable accept options to pre-customize the plot, for example `data.hvplot.explorer(title='Penguins', width=200)`.
24
+
25
+ ## Interface
26
+
27
+ The object returned by `explorer()` is a [Panel](https://panel.holoviz.org/) layout that can be displayed in a notebook or served in a web application. This small application includes:
28
+
29
+ - right-hand side: a preview of the hvPlot plot and code you are building
30
+ - left-hand side: the various options that you can set to customize the plot
31
+ - top part: an Alert section that displays error messages
32
+ - bottom part: a status bar which includes a *live update* checkbox to disable live updating the preview
33
+
34
+ Let's create our first explorer instance.
35
+
36
+
37
+ ```python
38
+ from bokeh.sampledata.penguins import data as df
39
+
40
+ df.head(2)
41
+ ```
42
+
43
+
44
+ ```python
45
+ hvexplorer = df.hvplot.explorer()
46
+ hvexplorer
47
+ ```
48
+
49
+ Spend some time browsing the options made available to you. Note however that to be fully interactive the explorer needs to be executed with a live Python kernel, updating the options on the website won't update the plot.
50
+
51
+ Before diving more into the explorer's capabilities, we will update the explorer we just created as the default configuration doesn't lead to a very interesting preview for this dataset. We will do so programmatically for the purpose of building this website but you would usually not have to do that, so just assume you've changed a few options directly in the explorer using your mouse and keyboard.
52
+
53
+
54
+ ```python
55
+ hvexplorer.param.update(x='bill_length_mm', y_multi=['bill_depth_mm'], by=['species'])
56
+ hvexplorer.labels.title = 'Penguins Scatter'
57
+ ```
58
+
59
+ ### Record the plot state
60
+
61
+ Quite often you will want to record the state of a plot you have obtained from the explorer. We even encourage the pattern of creating short-lived explorer instances that allow for quickly building the plots you want, record their state and then remove the instances from your notebook, possibly replacing them by simpler `.hvplot()` plot expressions.
62
+
63
+ You can record the state of an explorer instance in multiple ways:
64
+
65
+ - *Code* tab: displays a code snippet you can copy/paste in your notebook and that will generate exactly the same plot as previewed in the explorer
66
+ - `code` parameter: holds the code snippet string
67
+ - `.plot_code(var_name)` method: similar to the `code` parameter except you can configure the variable name
68
+ - `.settings()` method: to obtain a dictionary of your customized settings
69
+ - `.save(filename, **kwargs)` method: to save the plot to file
70
+ - `.hvplot()` method: to get a handle on the displayed HoloViews plot
71
+
72
+ We will explore a few of these approaches. Let's start with printng `code` and validating that is produces a snippet that can be copy/pasted into another cell and executed (using `eval` to simulate that).
73
+
74
+
75
+ ```python
76
+ print(hvexplorer.code)
77
+ ```
78
+
79
+
80
+ ```python
81
+ eval(hvexplorer.code)
82
+ ```
83
+
84
+ The dictionary obtained from calling `.settings()` can be directly passed as kwargs to the `.hvplot()` data accessor to re-create the customized plot using the plotting API.
85
+
86
+
87
+ ```python
88
+ settings = hvexplorer.settings()
89
+ settings
90
+ ```
91
+
92
+ Note that for the next line to display a plot `hvplot.pandas` has to be imported, which we did at the beginning of this notebook.
93
+
94
+
95
+ ```python
96
+ df.hvplot(**settings)
97
+ ```
98
+
99
+ ## Supported data inputs
100
+
101
+ The explorer was added in version `0.8.0` with support for Pandas DataFrames. Support for Xarray objects was added in version `0.9.0`.
102
+
103
+
104
+ ```python
105
+ ds = xr.tutorial.open_dataset('air_temperature')
106
+
107
+ hvplot.explorer(ds, x='lon', y='lat')
108
+ ```
109
+
110
+ ## Geographic options
111
+
112
+ When `geoviews` is installed, it's also possible to geographically reference the data.
113
+
114
+
115
+ ```python
116
+ hvexplorer = hvplot.explorer(ds, x='lon', y='lat', geo=True)
117
+ hvexplorer.geographic.param.update(crs='PlateCarree', tiles='CartoDark', global_extent=False)
118
+ hvexplorer
119
+ ```
120
+
121
+ ## Conclusion
122
+
123
+ The *Explorer* makes it very easy to quickly spin up a small application in a notebook with which you can explore your data, generate the visualization that you want, record it in a simple way, and keep going with your analysis!
hvplot_docs/Exporting_and_Archiving.md ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Exporting and Archiving
2
+
3
+ Most of the other user guides show you how to use HoloViews for interactive, exploratory visualization of your data, while the [Applying Customizations](03-Applying_Customizations.ipynb) user guide shows how to use HoloViews completely non-interactively, generating and rendering images directly to disk using `hv.save`. In this notebook, we show how HoloViews works together with the Jupyter Notebook to establish a fully interactive yet *also* fully reproducible scientific or engineering workflow for generating reports or publications. That is, as you interactively explore your data and build visualizations in the notebook, you can automatically generate and export them as figures that will feed directly into your papers or web pages, along with records of how those figures were generated and even storing the actual data involved so that it can be re-analyzed later.
4
+
5
+
6
+
7
+
8
+ ```python
9
+ import holoviews as hv
10
+ from holoviews import opts
11
+ from holoviews.operation import contours
12
+ hv.extension('matplotlib')
13
+ ```
14
+
15
+ ## Exporting specific files
16
+
17
+ During interactive exploration in the Jupyter Notebook, your results are always visible within the notebook itself, but you can explicitly request that any visualization is also exported to an external file on disk:
18
+
19
+
20
+ ```python
21
+ penguins = hv.RGB.load_image('../assets/penguins.png')
22
+ hv.save(penguins, 'penguin_plot.png', fmt='png')
23
+ penguins
24
+ ```
25
+
26
+ This mechanism can be used to provide a clear link between the steps for generating the figure, and the file on disk. You can now load the exported PNG image back into HoloViews, if you like, using ``hv.RGB.load_image`` although the result would be a bit confusing due to the nested axes.
27
+
28
+ The ``fig="png"`` part of the ``hv.save`` function call above specified that the file should be saved in PNG format, which is useful for posting on web pages or editing in raster-based graphics programs. Note that `hv.save` also accepts `HoloMap`s which can be saved to formats such as ``'scrubber'``, ``'widgets'`` or even ``'gif'`` or ``'mp4'`` (if the necessary matplotlib dependencies are available).
29
+
30
+ If the file extension is part of the filename, that will automatically be used to set the format. Conversely, if the format is explicitly specified, then the extension does not have to be part of the filename (and any filename extension that is provided will be ignored). Sometimes the two pieces of information are independent: for instance, a filename ending in `.html` can support either the `'widgets'` or `'scrubber'` formats.
31
+
32
+ For a publication, you will usually want to select SVG format because this vector format preserves the full resolution of all text and drawing elements. SVG files can be be used in some document preparation programs directly (e.g. [LibreOffice](http://www.libreoffice.org/)), and can easily be converted and manipulated in vector graphics editors such as [Inkscape](https://inkscape.org).
33
+
34
+ ## Exporting notebooks
35
+
36
+ The ``hv.save`` function is useful when you want specific plots saved into specific files. Often, however, a notebook will contain an entire suite of results contained in multiple different cells, and manually specifying these cells and their filenames is error-prone, with a high likelihood of accidentally creating multiple files with the same name or using different names in different notebooks for the same objects.
37
+
38
+ To make the exporting process easier for large numbers of outputs, as well as more predictable, HoloViews also offers a powerful automatic notebook exporting facility, creating an archive of all your results. Automatic export is very useful in the common case of having a notebook that contains a series of figures to be used in a report or publication, particularly if you are repeatedly re-running the notebook as you finalize your results, and want the full set of current outputs to be available to an external document preparation system.
39
+
40
+ The advantage of using this archival system over simply converting the notebook to a static HTML file with nbconvert is that you can generate a collection of individual file assets in one or more desired file formats.
41
+
42
+ To turn on automatic adding of your files to the export archive, run ``hv.archive.auto()``:
43
+
44
+
45
+ ```python
46
+ hv.archive.auto()
47
+ ```
48
+
49
+ This object's behavior can be customized extensively; try pressing tab within the parentheses for a list of options, which are described more fully below.
50
+
51
+ By default, the output will go into a directory with the same name as your notebook, and the names for each object will be generated from the groups and labels used by HoloViews. Objects that contain HoloMaps are not exported by default, since those are usually rendered as animations that are not suitable for inclusion in publications, but you can change it to ``.auto(holomap='gif')`` if you want those as well.
52
+
53
+ ### Adding files to an archive
54
+
55
+ To see how the auto-exporting works, let's define a few HoloViews objects:
56
+
57
+
58
+ ```python
59
+ penguins[:,:,'R'].relabel("Red") + penguins[:,:,'G'].relabel("Green") + penguins[:,:,'B'].relabel("Blue")
60
+ ```
61
+
62
+
63
+ ```python
64
+ penguins * hv.Arrow(0.15, 0.3, 'Penguin', '>')
65
+ ```
66
+
67
+
68
+ ```python
69
+ cs = contours(penguins[:,:,'R'], levels=[0.10,0.80])
70
+ overlay = penguins[:, :, 'R'] * cs
71
+ overlay.opts(
72
+ opts.Contours(linewidth=1.3, cmap='Autumn'),
73
+ opts.Image(cmap="gray"))
74
+ ```
75
+
76
+ We can now list what has been captured, along with the names that have been generated:
77
+
78
+
79
+ ```python
80
+ hv.archive.contents()
81
+ ```
82
+
83
+ Here each object has resulted in two files, one in SVG format and one in Python "pickle" format (which appears as a ``zip`` file with extension ``.hvz`` in the listing). We'll ignore the pickle files for now, focusing on the SVG images.
84
+
85
+ The name generation code for these files is heavily customizable, but by default it consists of a list of dimension values and objects:
86
+
87
+ ``{dimension},{dimension},...{group}-{label},{group}-{label},...``.
88
+
89
+ The ``{dimension}`` shows what dimension values are included anywhere in this object, if it contains any high-level ``Dimensioned`` objects like ``HoloMap``, ``NdOverlay``, and ``GridSpace``. Of course, nearly all HoloViews objects have dimensions, such as ``x`` and ``y`` in this case, but those dimensions are not used in the filenames because they are explicitly shown in the plots; only the top-level dimensions are used (those that determine which plot this is, not those that are shown in the plot itself.)
90
+
91
+ The ``{group}-{label}`` information lists the names HoloViews uses for default titles and for attribute access for the various objects that make up a given displayed object. E.g. the first SVG image in the list is a ``Layout`` of the three given ``Image`` objects, and the second one is an ``Overlay`` of an ``RGB`` object and an ``Arrow`` object. This information usually helps distinguish one plot from another, because they will typically be plots of objects that have different labels.
92
+
93
+ If the generated names are not unique, a numerical suffix will be added to make them unique. A maximum filename length is enforced, which can be set with ``hv.archive.max_filename=``_num_.
94
+
95
+ If you prefer a fixed-width filename, you can use a hash for each name instead (or in addition), where ``:.8`` specifies how many characters to keep from the hash:
96
+
97
+
98
+ ```python
99
+ hv.archive.filename_formatter="{SHA:.8}"
100
+ cs
101
+ ```
102
+
103
+
104
+ ```python
105
+ hv.archive.contents()
106
+ ```
107
+
108
+ You can see that the newest files added have the shorter, fixed-width format, though the names are no longer meaningful. If the ``filename_formatter`` had been set from the start, all filenames would have been of this type, which has both practical advantages (short names, all the same length) and disadvantages (no semantic clue about the contents).
109
+
110
+ ### Generated indexes
111
+
112
+ In addition to the files that were added to the archive for each of the cell outputs above, the archive exporter will also add an ``index.html`` file with a static copy of the notebook, with each cell labelled with the filename used to save it once `hv.archive.export()` is called (you can verify this for yourself after this call is executed below). This HTML file acts as a definitive index to your results, showing how they were generated and where they were exported on disk.
113
+
114
+ The exporter will also add a cleared, runnable copy of the notebook ``index.ipynb`` (with output deleted), so that you can later regenerate all of the output, with changes if necessary.
115
+
116
+ The exported archive will thus be a complete set of your results, along with a record of how they were generated, plus a recipe for regenerating them -- i.e., fully reproducible research! This HTML file and .ipynb file can the be submitted as supplemental materials for a paper, allowing any reader to build on your results, or it can just be kept privately so that future collaborators can start where this research left off.
117
+
118
+ ### Adding your own data to the archive
119
+
120
+ Of course, your results may depend on a lot of external packages, libraries, code files, and so on, which will not automatically be included or listed in the exported archive.
121
+
122
+ Luckily, the archive support is very general, and you can add any object to it that you want to be exported along with your output. For instance, you can store arbitrary metadata of your choosing, such as version control information, here as a JSON-format text file:
123
+
124
+
125
+ ```python
126
+ import json
127
+ hv.archive.add(filename='metadata.json',
128
+ data=json.dumps({'repository':'git@github.com:holoviz/holoviews.git',
129
+ 'commit':'437e8d69'}), info={'mime_type':'text/json'})
130
+ ```
131
+
132
+ The new file can now be seen in the contents listing:
133
+
134
+
135
+ ```python
136
+ hv.archive.contents()
137
+ ```
138
+
139
+ You can get a more direct list of filenames using the ``listing`` method:
140
+
141
+
142
+ ```python
143
+ listing = hv.archive.listing()
144
+ listing
145
+ ```
146
+
147
+ In this way, you should be able to automatically generate output files, with customizable filenames, storing any data or metadata you like along with them so that you can keep track of all the important information for reproducing these results later.
148
+
149
+ ### Controlling the behavior of ``hv.archive``
150
+
151
+ The ``hv.archive`` object provides numerous parameters that can be changed. You can e.g.:
152
+
153
+ - output the whole directory to a single compressed ZIP or tar archive file (e.g. ``hv.archive.param.update(pack=False, archive_format='zip')`` or ``archive_format='tar'``)
154
+
155
+ - generate a new directory or archive every time the notebook is run (``hv.archive.uniq_name=True``); otherwise the old output directory is erased each time
156
+
157
+ - choose your own name for the output directory or archive (e.g. ``hv.archive.export_name="{timestamp}"``)
158
+
159
+ - change the format of the optional timestamp (e.g. to retain snapshots hourly, ``archive.param.update(export_name="{timestamp}", timestamp_format="%Y_%m_%d-%H")``)
160
+
161
+ - select PNG output, at a specified rendering resolution: ``hv.archive.exporters=[hv.renderer('bokeh').instance(size=50)])
162
+ ``
163
+
164
+ These options and any others listed above can all be set in the ``hv.archive.auto()`` call at the start, for convenience and to ensure that they apply to all of the files that are added.
165
+
166
+ ### Writing the archive to disk
167
+
168
+ To actually write the files you have stored in the archive to disk, you need to call ``export()`` after any cell that might contain computation-intensive code. Usually it's best to do so as the last or nearly last cell in your notebook, though here we do it earlier because we wanted to show how to use the exported files.
169
+
170
+
171
+ ```python
172
+ hv.archive.export()
173
+ ```
174
+
175
+ Shortly after the ``export()`` command has been executed, the output should be available as a directory on disk, by default in the same directory as the notebook file, named with the name of the notebook:
176
+
177
+
178
+ ```python
179
+ import os
180
+ os.getcwd()
181
+ if os.path.exists(hv.archive.notebook_name):
182
+ print('\n'.join(sorted(os.listdir(hv.archive.notebook_name))))
183
+ ```
184
+
185
+ For technical reasons to do with how the IPython Notebook interacts with JavaScript, if you use the Jupyter Notebook command ``Run all``, the ``hv.archive.export()`` command is not actually executed when the cell with that call is encountered during the run. Instead, the ``export()`` is queued until after the final cell in the notebook has been executed. This asynchronous execution has several awkward but not serious consequences:
186
+
187
+ - It is not possible for the ``export()`` cell to show whether any errors were encountered during exporting, because these will not occur until after the notebook has completed processing. To see any errors, you can run ``hv.archive.last_export_status()`` separately, *after* the ``Run all`` has completed. E.g. just press shift-[Enter] in the following cell, which will tell you whether the previous export was successful.
188
+
189
+ - If you use ``Run all``, the directory listing ``os.listdir()`` above will show the results from the *previous* time this notebook was run, since it executes before the export. Again, you can use shift-[Enter] to update the data once complete.
190
+
191
+ - The ``Export name:`` in the output of ``hv.archive.export()`` will not always show the actual name of the directory or archive that will be created. In particular, it may say ``{notebook}``, which when saving will actually expand to the name of your Jupyter Notebook.
192
+
193
+
194
+ ```python
195
+ hv.archive.last_export_status()
196
+ ```
197
+
198
+ ### Accessing your saved data
199
+
200
+ By default, HoloViews saves not only your rendered plots (PNG, SVG, etc.), but also the actual HoloViews objects that the plots visualize, which contain all your actual data. The objects are stored in compressed Python pickle files (``.hvz``), which are visible in the directory listings above but have been ignored until now. The plots are what you need for writing a document, but the raw data is is a crucial record to keep as well. For instance, you now can load in the HoloViews object, and manipulate it just as you could when it was originally defined. E.g. we can re-load our ``Levels`` ``Overlay`` file, which has the contours overlaid on top of the image, and easily pull out the underlying ``Image`` object:
201
+
202
+
203
+ ```python
204
+ import os
205
+ from holoviews.core.io import Unpickler
206
+ c, a = None,None
207
+ hvz_file = [f for f in listing if f.endswith('hvz')][0]
208
+ path = os.path.join(hv.archive.notebook_name, hvz_file)
209
+
210
+ if os.path.isfile(path):
211
+ print('Unpickling {filename}'.format(filename=hvz_file))
212
+ obj = Unpickler.load(open(path,"rb"))
213
+ print(obj)
214
+ else:
215
+ print('Could not find file {path}'.format(path=path))
216
+ print('Current directory is {cwd}'.format(cwd=os.getcwd()))
217
+ print('Containing files and directories: {listing}'.format(listing=os.listdir(os.getcwd())))
218
+ ```
219
+
220
+ Given the ``Image``, you can also access the underlying array data, because HoloViews objects are simply containers for your data and associated metadata. This means that years from now, as long as you can still run HoloViews, you can now easily re-load and explore your data, plotting it entirely different ways or running different analyses, even if you no longer have any of the original code you used to generate the data. All you need is HoloViews, which is permanently archived on GitHub and is fully open source and thus should always remain available. Because the data is stored conveniently in the archive alongside the figure that was published, you can see immediately which file corresponds to the data underlying any given plot in your paper, and immediately start working with the data, rather than laboriously trying to reconstruct the data from a saved figure.
221
+
222
+ If you do not want the pickle files, you can of course turn them off if you prefer, by changing ``hv.archive.auto()`` to:
223
+
224
+ ```python
225
+ hv.archive.auto(exporters=[hv.renderer('matplotlib').instance(holomap=None)])
226
+ ```
227
+
228
+ Here, the exporters list has been updated to include the usual default exporters *without* the `Pickler` exporter that would usually be included.
229
+
230
+ ## Using HoloViews to do reproducible research
231
+
232
+ The export options from HoloViews help you establish a feasible workflow for doing reproducible research: starting from interactive exploration, either export specific files with ``hv.save``, or enable ``hv.archive.auto()``, which will store a copy of your notebook and its output ready for inclusion in a document but retaining the complete recipe for reproducing the results later.
233
+
234
+ ### Why reproducible research matters
235
+
236
+ To understand why these capabilities are important, let's consider the process by which scientific results are typically generated and published without HoloViews. Scientists and engineers use a wide variety of data-analysis tools, ranging from GUI-based programs like Excel spreadsheets, mixed GUI/command-line programs like Matlab, or purely scriptable tools like matplotlib or bokeh. The process by which figures are created in any of these tools typically involves copying data from its original source, selecting it, transforming it, choosing portions of it to put into a figure, choosing the various plot options for a subfigure, combining different subfigures into a complete figure, generating a publishable figure file with the full figure, and then inserting that into a report or publication.
237
+
238
+ If using GUI tools, often the final figure is the only record of that process, and even just a few weeks or months later a researcher will often be completely unable to say precisely how a given figure was generated. Moreover, this process needs to be repeated whenever new data is collected, which is an error-prone and time-consuming process. The lack of records is a serious problem for building on past work and revisiting the assumptions involved, which greatly slows progress both for individual researchers and for the field as a whole. Graphical environments for capturing and replaying a user's GUI-based workflow have been developed, but these have greatly restricted the process of exploration, because they only support a few of the many analyses required, and thus they have rarely been successful in practice. With GUI tools it is also very difficult to "curate" the sequence of steps involved, i.e., eliminating dead ends, speculative work, and unnecessary steps, with a goal of showing the clear path from incoming data to a final figure.
239
+
240
+ In principle, using scriptable or command-line tools offers the promise of capturing the steps involved, in a form that can be curated. In practice, however, the situation is often no better than with GUI tools, because the data is typically taken through many manual steps that culminate in a published figure, and without a laboriously manually created record of what steps are involved, the provenance of a given figure remains unknown. Where reproducible workflows are created in this way, they tend to be "after the fact", as an explicit exercise to accompany a publication, and thus (a) they are rarely done, (b) they are very difficult to do if any of the steps were not recorded originally.
241
+
242
+ A Jupyter notebook helps significantly to make the scriptable-tools approach viable, by recording both code and the resulting output, and can thus in principle act as a record for establishing the full provenance of a figure. But because typical plotting libraries require so much plotting-specific code before any plot is visible, the notebook quickly becomes unreadable. To make notebooks readable, researchers then typically move the plotting code for a specific figure to some external file, which then drifts out of sync with the notebook so that the notebook no longer acts as a record of the link between the original data and the resulting figure.
243
+
244
+ HoloViews provides the final missing piece in this approach, by allowing researchers to work directly with their data interactively in a notebook, using small amounts of code that focus on the data and analyses rather than plotting code, yet showing the results directly alongside the specification for generating them. This user guide will describe how use a Jupyter notebook with HoloViews to export your results in a way that preserves the information about how those results were generated, providing a clear chain of provenance and making reproducible research practical at last.
245
+
246
+ For more information on how HoloViews can help build a reproducible workflow, see our [2015 paper on using HoloViews for reproducible research](http://conference.scipy.org/proceedings/scipy2015/pdfs/jean-luc_stevens.pdf).
hvplot_docs/Geographic_Data.md ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import xarray as xr
3
+ import hvplot.pandas # noqa
4
+ import hvplot.xarray # noqa
5
+ import cartopy.crs as ccrs
6
+
7
+ from bokeh.sampledata.airport_routes import airports
8
+ ```
9
+
10
+ ## Installation
11
+
12
+ The plot API also has support for geographic data built on top of Cartopy and GeoViews. Both can be installed using conda with:
13
+
14
+ conda install geoviews
15
+
16
+ or with pip:
17
+
18
+ pip install geoviews
19
+
20
+ ## Usage
21
+
22
+ Only certain hvPlot types support geographic coordinates, currently including: 'points', 'polygons', 'paths', 'image', 'quadmesh', 'contour', and 'contourf'. As an initial example, consider a dataframe of all US airports (including military bases overseas):
23
+
24
+
25
+ ```python
26
+ airports.head(3)
27
+ ```
28
+
29
+ ### Plotting points
30
+
31
+ If we want to overlay our data on geographic maps or reproject it into a geographic plot, we can set ``geo=True``, which declares that the data will be plotted in a geographic coordinate system. The default coordinate system is the ``PlateCarree`` projection, i.e., raw longitudes and latitudes. If the data is in another coordinate system, you will need to [declare an explicit ``crs``](#Declaring-a-CRS) as an argument, in which case `geo=True` is assumed. Once hvPlot knows that your data is in geo coordinates, you can use the ``tiles`` option to overlay a the plot on top of map tiles.
32
+
33
+
34
+ ```python
35
+ airports.hvplot.points('Longitude', 'Latitude', geo=True, color='red', alpha=0.2,
36
+ xlim=(-180, -30), ylim=(0, 72), tiles='ESRI')
37
+ ```
38
+
39
+ ### Declaring a CRS
40
+
41
+ To declare a geographic plot we have to supply a ``cartopy.crs.CRS`` (or coordinate reference system). Coordinate reference systems are described in the [GeoViews documentation](https://geoviews.org/user_guide/Projections.html) and the full list of available CRSs is in the [cartopy documentation](https://scitools.org.uk/cartopy/docs/v0.15/crs/projections.html).
42
+
43
+ ### Geopandas
44
+
45
+ Since a GeoPandas ``DataFrame`` is just a Pandas DataFrames with additional geographic information, it inherits the ``.hvplot`` method. We can thus easily load shapefiles and plot them on a map:
46
+
47
+
48
+ ```python
49
+ import geopandas as gpd
50
+
51
+ cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))
52
+
53
+ cities.hvplot(global_extent=True, frame_height=450, tiles=True)
54
+ ```
55
+
56
+ The GeoPandas support allows plotting ``GeoDataFrames`` containing ``'Point'``, ``'Polygon'``, ``'LineString'`` and ``'LineRing'`` geometries, but not ones containing a mixture of different geometry types. Calling ``.hvplot`` will automatically figure out the geometry type to plot, but it also possible to call ``.hvplot.points``, ``.hvplot.polygons``, and ``.hvplot.paths`` explicitly.
57
+
58
+ To draw multiple GeoDataFrames onto the same plot, use the ``*`` operator:
59
+
60
+
61
+ ```python
62
+ world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
63
+
64
+ world.hvplot(geo=True) * cities.hvplot(geo=True, color='orange')
65
+ ```
66
+
67
+ It is possible to declare a specific column to use as color with the ``c`` keyword:
68
+
69
+
70
+ ```python
71
+ world.hvplot(geo=True) + world.hvplot(c='continent', geo=True)
72
+ ```
73
+
74
+ ## Spatialpandas
75
+
76
+ Spatialpandas is another powerful library for working with geometries and is optimized for rendering with datashader, making it possible to plot millions of individual geometries very quickly:
77
+
78
+
79
+ ```python
80
+ import spatialpandas as spd
81
+
82
+ spd_world = spd.GeoDataFrame(world)
83
+
84
+ spd_world.hvplot(datashade=True, project=True, aggregator='count_cat', c='continent', color_key='Category10')
85
+ ```
86
+
87
+ ### Declaring an output projection
88
+
89
+ The ``crs=`` argument specifies the *input* projection, i.e. it declares how to interpret the incoming data values. You can independently choose any *output* projection, i.e. how you want to map the data points onto the screen for display, using the ``projection=`` argument. After loading the same temperature dataset explored in the [Gridded Data](Gridded_Data.ipynb) section, the data can be displayed on an Orthographic projection:
90
+
91
+
92
+ ```python
93
+ air_ds = xr.tutorial.open_dataset('air_temperature').load()
94
+
95
+ air_ds.hvplot.quadmesh(
96
+ 'lon', 'lat', 'air', projection=ccrs.Orthographic(-90, 30),
97
+ global_extent=True, frame_height=540, cmap='viridis',
98
+ coastline=True
99
+ )
100
+ ```
101
+
102
+ If you don't need to pass any keyword arguments to a given projection and you don't have cartopy.crs (ccrs) imported, you can use the string representation: e.g. ``'LambertConformal'`` instead of ``ccrs.LambertConformal()``. Note that it is case sensitive!
103
+
104
+
105
+ ```python
106
+ air_ds.hvplot.quadmesh(
107
+ 'lon', 'lat', 'air', projection='LambertConformal',
108
+ )
109
+ ```
110
+
111
+ Note that when displaying raster data in a projection other than the one in which the data is stored, it is more accurate to render it as a ``quadmesh`` rather than an ``image``. As you can see above, a QuadMesh will project each original bin or pixel into the correct non-rectangular shape determined by the projection, accurately showing the geographic extent covered by each sample. An Image, on the other hand, will always be rectangularly aligned in the 2D plane, which requires warping and resampling the data in a way that allows efficient display but loses accuracy at the pixel level. Unfortunately, rendering a large QuadMesh using Bokeh can be very slow, but there are two useful alternatives for datasets too large to be practical as native QuadMeshes.
112
+
113
+ The first is using the ``rasterize`` or ``datashade`` options to regrid the data before rendering it, i.e., rendering the data on the backend and then sending a more efficient image-based representation to the browser. One thing to note when using these operations is that it may be necessary to project the data **before** rasterizing it, e.g. to address wrapping issues. To do this provide ``project=True``, which will project the data before it is rasterized (this also works for other types and even when not using these operations). Another reason why this is important when rasterizing the data is that if the CRS of the data does not match the displayed projection, all the data will be projected every time you zoom or pan, which can be very slow. Deciding whether to ``project`` is therefore a tradeoff between projecting the raw data ahead of time or accepting the overhead on dynamic zoom and pan actions.
114
+
115
+
116
+ ```python
117
+ rasm = xr.tutorial.open_dataset('rasm').load()
118
+
119
+
120
+
121
+ rasm.hvplot.quadmesh(
122
+ 'xc', 'yc', crs=ccrs.PlateCarree(), projection=ccrs.PlateCarree(),
123
+ ylim=(0, 90), cmap='viridis', project=True, geo=True,
124
+ rasterize=True, coastline=True, frame_width=800, dynamic=False,
125
+ )
126
+ ```
127
+
128
+ Another option that's still relatively slow for larger data but avoids sending large data into your browser is to plot the data using ``contour`` and ``contourf`` visualizations, generating a line or filled contour with a discrete number of levels:
129
+
130
+
131
+ ```python
132
+ rasm.hvplot.contourf(
133
+ 'xc', 'yc', crs=ccrs.PlateCarree(), projection=ccrs.PlateCarree(),
134
+ ylim=(0, 90), frame_width=800, cmap='viridis', levels=10,
135
+ coastline=True
136
+ )
137
+ ```
138
+
139
+ As you can see, hvPlot makes it simple to work with geographic data visually. For more complex plot types and additional details, see the [GeoViews](https://geoviews.org) documentation.
140
+
141
+ ## Geographic options
142
+
143
+ The API provides various geo-specific options:
144
+
145
+ - ``coastline`` (default=False): Whether to display a coastline on top of the plot, setting ``coastline='10m'/'50m'/'110m'`` specifies a specific scale
146
+ - ``crs`` (default=None): Coordinate reference system of the data specified as Cartopy CRS object, proj.4 string or EPSG code
147
+ - ``features`` features (default=None): A list of features or a dictionary of features and the scale at which to render it. Available features include 'borders', 'coastline', 'lakes', 'land', 'ocean', 'rivers' and 'states'. Available scales include '10m'/'50m'/'110m'.
148
+ - ``geo`` (default=False): Whether the plot should be treated as geographic (and assume PlateCarree, i.e. lat/lon coordinates)
149
+ - ``global_extent`` (default=False): Whether to expand the plot extent to span the whole globe
150
+ - ``project`` (default=False): Whether to project the data before plotting (adds initial overhead but avoids projecting data when plot is dynamically updated)
151
+ - ``tiles`` (default=False): Whether to overlay the plot on a tile source. Tiles sources can be selected by name, the default is 'Wikipedia'.
152
+ Other options are: 'CartoDark', 'CartoEco', 'CartoLight', 'CartoMidnight', 'EsriImagery', 'EsriNatGeo', 'EsriReference''EsriTerrain', 'EsriUSATopo', 'OSM', 'StamenLabels', 'StamenTerrain', 'StamenTerrainRetina', 'StamenToner', 'StamenTonerBackground', 'StamenWatercolor'. Stamen tile sources require a Stadia account when not running locally; see [stadiamaps.com](https://stadiamaps.com/).
hvplot_docs/Geometry_Data.md ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ In addition to the two main types of data, namely tabular/columnar and gridded data HoloViews also provide extensible interfaces to represent path geometry data. Specifically it has three main element types used to representing different types of geometries. In this section we will cover the HoloViews data model for representing different kinds of geometries.
2
+
3
+ There are many different ways of representing path geometries but HoloViews' data model is oriented on GEOS geometry definitions and allows faithfully round-tripping data between its element types and GEOS geometry definitions such as ``LinearString``, ``Polygon``, ``MultiLineString`` and ``MultiPolygon`` geometries (even if this is not implemented in HoloViews itself). HoloViews defines a dictionary based format for the geometries but also supports [spatialpandas](https://github.com/holoviz/spatialpandas), which is a highly optimized implementation similar to [geopandas](https://github.com/geopandas/geopandas/) but without the heavy geo-dependencies such as shapely and fiona. [GeoViews](https://geoviews.org/user_guide/Geometries.html) supports both geopandas and raw shapely geometries directly.
4
+
5
+
6
+ ```python
7
+ import numpy as np
8
+ import holoviews as hv
9
+ from holoviews import opts
10
+
11
+ hv.extension('bokeh')
12
+ ```
13
+
14
+ ## Representing paths
15
+
16
+ The ``Path`` element represents a collection of path geometries with optional associated values. Each path geometry may be split into sub-geometries on NaN-values and may be associated with scalar values or array values varying along its length. In analogy to GEOS geometry types a Path is a collection of LineString and MultiLineString geometries with associated values.
17
+
18
+ While other formats can be supported through extensible interfaces (e.g. geopandas and shapely objects in GeoViews), natively HoloViews provides support for representing paths as one or more columnar data-structures including arrays, dataframes and dictionaries of column arrays and scalars. A simple path geometry may therefore be drawn using:
19
+
20
+
21
+ ```python
22
+ hv.Path({'x': [1, 2, 3, 4, 5], 'y': [0, 0, 1, 1, 2]}, ['x', 'y'])
23
+ ```
24
+
25
+ Here the dictionary of x- and y-coordinates could also be an NumPy array with two columns or a dataframe with 'x' and 'y' columns.
26
+
27
+ To draw multiple paths the data-structures can be wrapped in a list. Additionally, it is also possible to associate a value with each path by declaring it as a value dimension:
28
+
29
+
30
+ ```python
31
+ p = hv.Path([{'x': [1, 2, 3, 4, 5], 'y': [0, 0, 1, 1, 2], 'value': 0},
32
+ {'x': [5, 4, 3, 2, 1], 'y': [2, 2, 1, 1, 0], 'value': 1}], vdims='value').opts(color='value')
33
+ p
34
+ ```
35
+
36
+ #### Multi-geometry
37
+
38
+ Splitting the geometries in this way allows assigning separate values to each geometry, however often multiple geometries share the same value in which case it may be desirable to represent them as a multi-geometry by combining the coordinates and separating them by a NaN value:
39
+
40
+
41
+ ```python
42
+ hv.Path([{'x': [1, 2, 3, 4, 5, np.nan, 5, 4, 3, 2, 1],
43
+ 'y': [0, 0, 1, 1, 2, np.nan, 2, 2, 1, 1, 0], 'value': 0}],
44
+ vdims='value').opts(color='value')
45
+ ```
46
+
47
+ This represents a more efficient format particularly when there are very many small geometries with the same value.
48
+
49
+ #### Scalar vs. continuously varying value dimensions
50
+
51
+ Unlike ``Contours`` which are limited to representing iso-contours or isoclines, i.e. a function of two variables which describes a curve along which the function has a constant value, a ``Path`` element may also have continuously varying values along its path. Below we will declare a path with a value that varies along its path:
52
+
53
+
54
+ ```python
55
+ a, b, delta = 3, 5, np.pi/2.
56
+
57
+ vs = np.linspace(0, np.pi*2, 200)
58
+ xs = np.sin(a * vs + delta)
59
+ ys = np.sin(b * vs)
60
+
61
+ hv.Path([{'x': xs, 'y': ys, 'value': vs}], vdims='value').opts(
62
+ color='value', cmap='hsv')
63
+ ```
64
+
65
+ Note that since not all data formats allow storing scalar values as actual scalars, 1D-arrays matching the length of the coordinates but with only one unique value are also considered scalar. For example the following is a valid ``Contours`` element despite the fact that the value dimension is not a scalar variable:
66
+
67
+
68
+ ```python
69
+ hv.Contours([{'x': xs, 'y': ys, 'value': np.ones(200)}], vdims='value').opts(color='value')
70
+ ```
71
+
72
+ ## Representing Polygons
73
+
74
+ The ``Polygons`` element represents a collection of polygon geometries with associated scalar values. Each polygon geometry may be split into sub-geometries on NaN-values and may be associated with scalar values. In analogy to GEOS geometry types a ``Polygons`` element is a collection of Polygon and MultiPolygon geometries. Polygon geometries are defined as a set of coordinates describing the exterior bounding ring and any number of interior holes.
75
+
76
+ In summary ``Polygons`` can be represented in much the same way as ``Paths`` above but have a special reserved key to store the polygon interiors or 'holes'. The holes are stored as a list-of-lists of arrays. This nested format is necessary to unambiguously associate holes with the sub-geometries in a multi-geometry. In the simplest case of a single Polygon geometry the format looks like this:
77
+
78
+
79
+ ```python
80
+ xs = [1, 2, 3]
81
+ ys = [2, 0, 7]
82
+ holes = [[[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]]]
83
+
84
+ hv.Polygons([{'x': xs, 'y': ys, 'holes': holes}])
85
+ ```
86
+
87
+ The 'x' and 'y' coordinates represent the exterior of the Polygon and the list-of-list of holes defines two interior regions inside the polygon.
88
+
89
+ In a multi-Polygon arrangement where two Polygon geometries are separated by NaNs, the purpose of the nested format becomes a bit clearer. Here the polygon from above still has the two holes but the second polygon does not have any holes, which we declare with an empty list:
90
+
91
+
92
+ ```python
93
+ xs = [1, 2, 3, np.nan, 6, 7, 3]
94
+ ys = [2, 0, 7, np.nan, 7, 5, 2]
95
+
96
+ holes = [
97
+ [[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]],
98
+ []
99
+ ]
100
+
101
+ hv.Polygons([{'x': xs, 'y': ys, 'holes': holes}])
102
+ ```
103
+
104
+ If a polygon has no holes at all the 'holes' key may be omitted entirely:
105
+
106
+
107
+ ```python
108
+ hv.Polygons([{'x': xs, 'y': ys, 'holes': holes, 'value': 0},
109
+ {'x': [4, 6, 6], 'y': [0, 2, 1], 'value': 1},
110
+ {'x': [-3, -1, -6], 'y': [3, 2, 1], 'value': 3}], vdims='value')
111
+ ```
112
+
113
+ ## Accessing the data
114
+
115
+ To access the underlying data the geometry elements (``Path``/``Contours``/``Polygons``) implement a ``split`` method. By default it simply returns a list of elements, where each contains only one geometry:
116
+
117
+
118
+ ```python
119
+ poly = hv.Polygons([
120
+ {'x': xs, 'y': ys, 'holes': holes, 'value': 0},
121
+ {'x': [4, 6, 6], 'y': [0, 2, 1], 'value': 1}
122
+ ], vdims='value')
123
+
124
+ hv.Layout(poly.split())
125
+ ```
126
+
127
+ Using the ``datatype`` argument the data may instead be returned in the desired format, e.g. 'dictionary', 'array' or 'dataframe'. Here we return the 'dictionary' format:
128
+
129
+
130
+ ```python
131
+ poly.split(datatype='dictionary')
132
+ ```
133
+
134
+ Note that this conversion may be lossy if the converted format has no way of representing 'holes' or other data.
hvplot_docs/Gridded_Data.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ hvPlot provides one API to explore data of many different types. Previous sections have exclusively worked with tabular data stored in pandas (or pandas-like) DataFrames. The other most common type of data are n-dimensional arrays. hvPlot aims to eventually support different array libraries but for now focuses on [xarray](https://xarray.pydata.org/en/stable/). XArray provides a convenient and very powerful wrapper to label the axis and coordinates of multi-dimensional (n-D) arrays. This user guide will cover how to leverage ``xarray`` and ``hvplot`` to visualize and explore data of different dimensionality ranging from simple 1D data, to 2D image-like data, to multi-dimensional cubes of data.
2
+
3
+ For these examples we’ll use the North American air temperature dataset:
4
+
5
+
6
+ ```python
7
+ import xarray as xr
8
+ import hvplot.xarray # noqa
9
+
10
+ air_ds = xr.tutorial.open_dataset('air_temperature').load()
11
+ air = air_ds.air
12
+ air_ds
13
+ ```
14
+
15
+ ## 1D Plots
16
+
17
+ Selecting the data at a particular lat/lon coordinate we get a 1D dataset of air temperatures over time:
18
+
19
+
20
+ ```python
21
+ air1d = air.sel(lat=40, lon=285)
22
+ air1d.hvplot()
23
+ ```
24
+
25
+ Notice how the axes are already appropriately labeled, because xarray stores the metadata required. We can also further subselect the data and use `*` to overlay plots:
26
+
27
+
28
+ ```python
29
+ air1d_sel = air1d.sel(time='2013-01')
30
+ air1d_sel.hvplot(color='purple') * air1d_sel.hvplot.scatter(marker='o', color='blue', size=15)
31
+ ```
32
+
33
+
34
+ ```python
35
+ air.lat
36
+ ```
37
+
38
+ ### Selecting multiple
39
+
40
+ If we select multiple coordinates along one axis and plot a chart type, the data will automatically be split by the coordinate:
41
+
42
+
43
+ ```python
44
+ air.sel(lat=[20, 40, 60], lon=285).hvplot.line()
45
+ ```
46
+
47
+ To plot a different relationship we can explicitly request to display the latitude along the y-axis and use the ``by`` keyword to color each longitude (or 'lon') differently (note that this differs from the ``hue`` keyword xarray uses):
48
+
49
+
50
+ ```python
51
+ air.sel(time='2013-02-01 00:00', lon=[280, 285]).hvplot.line(y='lat', by='lon', legend='top_right')
52
+ ```
53
+
54
+ ## 2D Plots
55
+
56
+ By default the ``DataArray.hvplot()`` method generates an image if the data is two-dimensional.
57
+
58
+
59
+ ```python
60
+ air2d = air.sel(time='2013-06-01 12:00')
61
+ air2d.hvplot(width=400)
62
+ ```
63
+
64
+ Alternatively we can also plot the same data using the ``contour`` and ``contourf`` methods, which provide a ``levels`` argument to control the number of iso-contours to draw:
65
+
66
+
67
+ ```python
68
+ air2d.hvplot.contour(width=400, levels=20) + air2d.hvplot.contourf(width=400, levels=8)
69
+ ```
70
+
71
+ ## n-D Plots
72
+
73
+ If the data has more than two dimensions it will default to a histogram without providing it further hints:
74
+
75
+
76
+ ```python
77
+ air.hvplot()
78
+ ```
79
+
80
+ However we can tell it to apply a ``groupby`` along a particular dimension, allowing us to explore the data as images along that dimension with a slider:
81
+
82
+
83
+ ```python
84
+ air.hvplot(groupby='time', width=500)
85
+ ```
86
+
87
+ By default, for numeric types you'll get a slider and for non-numeric types you'll get a selector. Use ``widget_type`` and ``widget_location`` to control the look of the widget. To learn more about customizing widget behavior see [Widgets](Widgets.ipynb).
88
+
89
+
90
+ ```python
91
+ air.hvplot(groupby='time', width=600, widget_type='scrubber', widget_location='bottom')
92
+ ```
93
+
94
+ If we pick a different, lower dimensional plot type (such as a 'line') it will automatically apply a groupby over the remaining dimensions:
95
+
96
+
97
+ ```python
98
+ air.hvplot.line(width=600)
99
+ ```
100
+
101
+ ## Statistical plots
102
+
103
+ Statistical plots such as histograms, kernel-density estimates, or violin and box-whisker plots aggregate the data across one or more of the coordinate dimensions. For instance, plotting a KDE provides a summary of all the air temperature values but we can, once again, use the ``by`` keyword to view each selected latitude (or 'lat') separately:
104
+
105
+
106
+ ```python
107
+ air.sel(lat=[25, 50, 75]).hvplot.kde('air', by='lat', alpha=0.5)
108
+ ```
109
+
110
+ Using the ``by`` keyword we can break down the distribution of the air temperature across one or more variables:
111
+
112
+
113
+ ```python
114
+ air.hvplot.violin('air', by='lat', color='lat', cmap='Category20')
115
+ ```
116
+
117
+ ## Rasterizing
118
+
119
+ If you are plotting a large amount of data at once, you can consider using the hvPlot interface to [Datashader](https://datashader.org), which can be enabled simply by setting `rasterize=True`.
120
+
121
+ Note that by declaring that the data should not be grouped by another coordinate variable, i.e. by setting `groupby=[]`, we can plot all the datapoints, showing us the spread of air temperatures in the dataset:
122
+
123
+
124
+ ```python
125
+ air.hvplot.scatter('time', groupby=[], rasterize=True) *\
126
+ air.mean(['lat', 'lon']).hvplot.line('time', color='indianred')
127
+ ```
128
+
129
+ Here we also overlaid a non-datashaded line plot of the average temperature at each time. If you enable the appropriate hover tool, the overlaid data supports hovering and zooming even in a static export such as on a web server or in an email, while the raw-data plot has been aggregated spatially before it is sent to the browser, and thus it has only the fixed spatial binning available at that time. If you have a live Python process, the raw data will be aggregated each time you pan or zoom, letting you see the entire dataset regardless of size.
hvplot_docs/Installing_and_Configuring.md ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Installing and Configuring Holoviews
2
+
3
+ HoloViews can be installed on any platform where [NumPy](http://numpy.org) and Python 3 are available.
4
+
5
+ That said, HoloViews is designed to work closely with many other libraries, which can make installation and configuration more complicated. This user guide page describes some of these less-common or not-required options that may be helpful for some users.
6
+
7
+ ## Other installation options
8
+
9
+ The main [installation instructions](http://holoviews.org/#installation) should be sufficient for most users, but you may also want the [Matplotlib](http://matplotlib.org) and [Plotly](https://plot.ly/python/) backends, which are required for some of the examples:
10
+
11
+ conda install matplotlib plotly
12
+
13
+ HoloViews can also be installed using one of these `pip` commands:
14
+
15
+ pip install holoviews
16
+ pip install 'holoviews[recommended]'
17
+ pip install 'holoviews[extras]'
18
+ pip install 'holoviews[all]'
19
+
20
+ The first option installs just the bare library and the [NumPy](http://numpy.org) and [Param](https://github.com/holoviz/param) libraries, which is all you need on your system to generate and work with HoloViews objects without visualizing them. The other options install additional libraries that are often useful, with the `recommended` option being similar to the `conda` install command above.
21
+
22
+ Between releases, development snapshots are made available as conda packages:
23
+
24
+ conda install -c pyviz/label/dev holoviews
25
+
26
+ To get the very latest development version you can clone our git
27
+ repository and put it on the Python path:
28
+
29
+ git clone https://github.com/holoviz/holoviews.git
30
+ cd holoviews
31
+ pip install -e .
32
+
33
+ ## JupyterLab configuration
34
+
35
+ To work with JupyterLab you will also need the HoloViews JupyterLab
36
+ extension:
37
+
38
+ ```
39
+ conda install -c conda-forge jupyterlab
40
+ jupyter labextension install @pyviz/jupyterlab_pyviz
41
+ ```
42
+
43
+ Once you have installed JupyterLab and the extension launch it with:
44
+
45
+ ```
46
+ jupyter-lab
47
+ ```
48
+
49
+ ## ``hv.config`` settings
50
+
51
+ The default HoloViews installation will use the latest defaults and options available, which is appropriate for new users. If you want to work with code written for older HoloViews versions, you can use the top-level ``hv.config`` object to control various backwards-compatibility options:
52
+
53
+ * ``future_deprecations``: Enables warnings about future deprecations (introduced in 1.11).
54
+ * ``warn_options_call``: Warn when using the to-be-deprecated ``__call__`` syntax for specifying options, instead of the recommended ``.opts`` method.
55
+
56
+ It is recommended you set ``warn_options_call`` to ``True`` in your holoviews.rc file (see section below).
57
+
58
+ It is possible to set the configuration using `hv.config` directly:
59
+
60
+
61
+ ```python
62
+ import holoviews as hv
63
+ hv.config(future_deprecations=True)
64
+ ```
65
+
66
+ However, because in some cases this configuration needs to be declared before the plotting extensions are imported, the recommended way of setting configuration options is:
67
+
68
+
69
+ ```python
70
+ hv.extension('bokeh', config=dict(future_deprecations=True))
71
+ ```
72
+
73
+ In addition to backwards-compatibility options, ``hv.config`` holds some global options:
74
+
75
+ * ``image_rtol``: The tolerance used to enforce regular sampling for regular, gridded data. Used to validate ``Image`` data.
76
+
77
+ This option allows you to set the ``rtol`` parameter of [``Image``](../reference/elements/bokeh/Image.ipynb) elements globally.
78
+
79
+
80
+ ## Improved tab-completion
81
+
82
+ Both ``Layout`` and ``Overlay`` are designed around convenient tab-completion, with the expectation of upper-case names being listed first. In recent versions of Jupyter/IPython there has been a regression whereby the tab-completion is no longer case-sensitive. This can be fixed with:
83
+
84
+
85
+ ```python
86
+ import holoviews as hv
87
+ hv.extension(case_sensitive_completion=True)
88
+ ```
89
+
90
+ ## The holoviews.rc file
91
+
92
+ HoloViews searches for the first rc file it finds in the following places (in order):
93
+
94
+ 1. ``holoviews.rc`` in the parent directory of the top-level ``__init__.py`` file (useful for developers working out of the HoloViews git repo)
95
+ 2. ``~/.holoviews.rc``
96
+ 3. ``~/.config/holoviews/holoviews.rc``
97
+
98
+ The rc file location can be overridden via the ``HOLOVIEWSRC`` environment variable.
99
+
100
+ The rc file is a Python script, executed as HoloViews is imported. An example rc file to include various options discussed above might look like this:
101
+
102
+ ```
103
+ import holoviews as hv
104
+ hv.config(warn_options_call=True)
105
+ hv.extension.case_sensitive_completion=True
106
+ ```
107
+
hvplot_docs/Integrations.md ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import numpy as np
3
+
4
+ np.random.seed(1)
5
+ ```
6
+
7
+ ## Data sources
8
+
9
+ The `.hvplot()` plotting API supports a wide range of data sources. Most frequently, a special import can be executed to register the `.hvplot` accessor on a data type. For instance, importing `hvplot.pandas` registers the `.hvplot` accessor on Pandas `DataFrame` and `Series` objects, allowing to call `df.hvplot.line()`.
10
+
11
+ Among the data sources introduced below, Pandas](https://pandas.pydata.org) is the only library that doesn't need to be installed separately as it is a direct dependency of hvPlot.
12
+
13
+ :::{note}
14
+ Supporting so many data sources is hard work! We are aware that the support for some of them isn't as good as we would like. If you encounter any issue please report it <a href='https://github.com/holoviz/hvplot/'>on GitHub</a>, we always welcome Pull Requests too!
15
+ :::
16
+
17
+ ### Columnar/tabular
18
+
19
+ #### Pandas
20
+
21
+ `.hvplot()` supports [Pandas](https://pandas.pydata.org) `DataFrame` and `Series` objects.
22
+
23
+
24
+ ```python
25
+ import hvplot.pandas # noqa
26
+ import pandas as pd
27
+
28
+ df_pandas = pd.DataFrame(np.random.randn(1000, 4), columns=list('ABCD')).cumsum()
29
+ df_pandas.head(2)
30
+ ```
31
+
32
+
33
+ ```python
34
+ # Pandas DataFrame
35
+ df_pandas.hvplot.line(height=150)
36
+ ```
37
+
38
+
39
+ ```python
40
+ # Pandas Series
41
+ s_pandas = df_pandas['A']
42
+ s_pandas.hvplot.line(height=150)
43
+ ```
44
+
45
+ #### [Dask](https://www.dask.org)
46
+
47
+ `.hvplot()` supports [Dask](https://www.dask.org) `DataFrame` and `Series` objects.
48
+
49
+
50
+ ```python
51
+ import hvplot.dask # noqa
52
+ import dask
53
+
54
+ df_dask = dask.dataframe.from_pandas(df_pandas, npartitions=2)
55
+ df_dask
56
+ ```
57
+
58
+
59
+ ```python
60
+ # Dask DataFrame
61
+ df_dask.hvplot.line(height=150)
62
+ ```
63
+
64
+
65
+ ```python
66
+ # Dask Series
67
+ s_dask = df_dask['A']
68
+ s_dask.hvplot.line(height=150)
69
+ ```
70
+
71
+ #### GeoPandas
72
+
73
+ `.hvplot()` supports [GeoPandas](https://geopandas.org) `GeoDataFrame` objects.
74
+
75
+
76
+ ```python
77
+ import hvplot.pandas # noqa
78
+ import geopandas as gpd
79
+
80
+ p_geometry = gpd.points_from_xy(
81
+ x=[12.45339, 12.44177, 9.51667, 6.13000],
82
+ y=[41.90328, 43.93610, 47.13372, 49.61166],
83
+ crs='EPSG:4326'
84
+ )
85
+ p_names = ['Vatican City', 'San Marino', 'Vaduz', 'Luxembourg']
86
+ gdf = gpd.GeoDataFrame(dict(name=p_names), geometry=p_geometry)
87
+ gdf.head(2)
88
+ ```
89
+
90
+
91
+ ```python
92
+ # GeoPandas GeoDataFrame
93
+ gdf.hvplot.points(geo=True, tiles='CartoLight', frame_height=150, data_aspect=0.5)
94
+ ```
95
+
96
+ #### Ibis
97
+
98
+ [Ibis](https://ibis-project.org/) is the "portable Python dataframe library", it provides a unified interface to many data backends (e.g. DuckDB, SQLite, SnowFlake, Google BigQuery). `.hvplot()` supports [Ibis](https://ibis-project.org/) `Expr` objects.
99
+
100
+
101
+ ```python
102
+ import hvplot.ibis # noqa
103
+ import ibis
104
+
105
+ table = ibis.memtable(df_pandas.reset_index())
106
+ table
107
+ ```
108
+
109
+
110
+ ```python
111
+ # Ibis Expr
112
+ table.hvplot.line(x='index', height=150)
113
+ ```
114
+
115
+ #### Polars
116
+
117
+ :::{note}
118
+ Added in version `0.9.0`.
119
+ :::
120
+
121
+ :::{important}
122
+ While other data sources like `Pandas` or `Dask` have built-in support in HoloViews, as of version 1.17.1 this is not yet the case for `Polars`. You can track this [issue](https://github.com/holoviz/holoviews/issues/5939) to follow the evolution of this feature in HoloViews. Internally hvPlot simply selects the columns that contribute to the plot and casts them to a Pandas object using Polars' `.to_pandas()` method.
123
+ :::
124
+
125
+
126
+ ```python
127
+ import hvplot.polars # noqa
128
+ import polars
129
+
130
+ df_polars = polars.from_pandas(df_pandas)
131
+ df_polars.head(2)
132
+ ```
133
+
134
+ `.hvplot()` supports [Polars](https://www.pola.rs/) `DataFrame`, `LazyFrame` and `Series` objects.
135
+
136
+
137
+ ```python
138
+ # Polars DataFrame
139
+ df_polars.hvplot.line(y=['A', 'B', 'C', 'D'], height=150)
140
+ ```
141
+
142
+
143
+ ```python
144
+ # Polars LazyFrame
145
+ df_polars.lazy().hvplot.line(y=['A', 'B', 'C', 'D'], height=150)
146
+ ```
147
+
148
+
149
+ ```python
150
+ # Polars Series
151
+ df_polars['A'].hvplot.line(height=150)
152
+ ```
153
+
154
+ #### Rapids cuDF
155
+
156
+ :::{important}
157
+ [Rapids cuDF](https://docs.rapids.ai/api/cudf) is a Python **GPU** DataFrame library. Neither hvPlot's nor HoloViews' test suites currently run on a GPU part of their CI, as of versions 0.9.0 and 1.17.1, respectively. This is due to the non availability of machines equipped with a GPU on the free CI system we rely on (Github Actions). Therefore it's possible that support for cuDF gets degraded in hvPlot without us noticing it immediately. Please report any issue you might encounter.
158
+ :::
159
+
160
+ `.hvplot()` supports [cuDF](https://docs.rapids.ai/api/cudf) `DataFrame` and `Series` objects.
161
+
162
+ #### Fugue
163
+
164
+ :::{admonition} Experimental
165
+ :class: caution
166
+ [Fugue](https://fugue-tutorials.readthedocs.io/) support, added in version `0.9.0`, is experimental and may change in future versions.
167
+ :::
168
+
169
+ hvPlot adds the `hvplot` plotting extension to FugueSQL.
170
+
171
+
172
+ ```python
173
+ import hvplot.fugue # noqa
174
+ import fugue
175
+
176
+ fugue.api.fugue_sql(
177
+ """
178
+ OUTPUT df_pandas USING hvplot:line(
179
+ height=150,
180
+ )
181
+ """
182
+ )
183
+ ```
184
+
185
+ ### Multidimensional
186
+
187
+ #### Xarray
188
+
189
+ `.hvplot()` supports [XArray](https://xarray.pydata.org) `Dataset` and `DataArray` labelled multidimensional objects.
190
+
191
+
192
+ ```python
193
+ import hvplot.xarray # noqa
194
+ import xarray as xr
195
+
196
+ ds = xr.Dataset({
197
+ 'A': (['x', 'y'], np.random.randn(100, 100)),
198
+ 'B': (['x', 'y'], np.random.randn(100, 100))},
199
+ coords={'x': np.arange(100), 'y': np.arange(100)}
200
+ )
201
+ ds
202
+ ```
203
+
204
+
205
+ ```python
206
+ # Xarray Dataset
207
+ ds.hvplot.hist(height=150)
208
+ ```
209
+
210
+
211
+ ```python
212
+ # Xarray DataArray
213
+ ds['A'].hvplot.image(height=150)
214
+ ```
215
+
216
+ ### Catalog
217
+
218
+ #### Intake
219
+
220
+ `.hvplot()` supports [Intake](https://github.com/ContinuumIO/intake) `DataSource` objects.
221
+
222
+ ### Streaming
223
+
224
+ #### Streamz
225
+
226
+ `.hvplot()` supports [Streamz](https://streamz.readthedocs.io) `DataFrame`, `DataFrames`, `Series` and `Seriess` objects.
227
+
228
+ ### Graph
229
+
230
+ #### NetworkX
231
+
232
+ The hvPlot [NetworkX](https://networkx.github.io) plotting API is meant as a drop-in replacement for the `networkx.draw` methods. The `draw` and other `draw_<>` methods are available in the `hvplot.networkx` module.
233
+
234
+
235
+ ```python
236
+ import hvplot.networkx as hvnx
237
+ import networkx as nx
238
+
239
+ G = nx.petersen_graph()
240
+ hvnx.draw(G, with_labels=True, height=150)
241
+ ```
242
+
243
+ ## Plotting extensions
244
+
245
+ hvPlot is capable of producing plots with [Bokeh](https://www.bokeh.org) (default, interactive), [Matplotlib](https://matplotlib.org) (static) and [Plotly](https://plotly.com/python/) (interactive). Under the hood, hvPlot delegates plotting to HoloViews which itself calls these plotting libraries. This is why we call hvPlot a high-level plotting library!
246
+
247
+ Follow the [Plotting Extensions Guide](Plotting_Extensions.ipynb) for more information.
248
+
249
+ :::{note}
250
+ Similarly to having to support many data sources, supporting three plotting extensions is hard work! We are aware they are not supported equivalently, you will get best support for Bokeh, followed by Matplotlib and finally Plotly. If you encounter any issue with a specific plotting extension please report it <a href='https://github.com/holoviz/hvplot/'>on GitHub</a>, we always welcome Pull Requests too!
251
+ :::
hvplot_docs/Interactive.md ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import ipywidgets as ipw
3
+ import hvplot.xarray # noqa
4
+ import hvplot.pandas # noqa
5
+ import panel as pn
6
+ import pandas as pd
7
+ import panel.widgets as pnw
8
+ import xarray as xr
9
+ ```
10
+
11
+ Interactive command-line or notebook interfaces are incredibly powerful tools for quickly doing exploratory analysis, letting you supply arguments to Python methods and functions and see the results immediately. However, this process of exploration can be slow and awkward for large parameter spaces because it requires manually typing each argument value. To further ease exploratory workflows, hvPlot ships with a convenient `.interactive` API, which mirrors the regular API of your favorite data analysis libraries like Pandas, Dask, and xarray but makes it possible to pass in _widgets_ for each argument value, not just a constant. When the widgets are used, the output will dynamically update the full pipeline of method calls so that it works just as if that particular value had been specified in the call being wrapped.
12
+
13
+ In this user guide we will explore how to use the .interactive API on xarray and pandas objects:
14
+
15
+
16
+ ```python
17
+ ds = xr.tutorial.load_dataset('air_temperature')
18
+ ds
19
+ ```
20
+
21
+
22
+ ```python
23
+ from bokeh.sampledata.stocks import IBM
24
+
25
+ df = pd.DataFrame(IBM)
26
+ df['date'] = pd.to_datetime(df.date)
27
+ ```
28
+
29
+ ## Interactive widgets
30
+
31
+ We can supply both regular values, widgets and parameters as arguments to methods on the `.interactive` accessor. Here, we'll use widgets from the [Panel](https://panel.holoviz.org) library. The repr of the resulting object will contain a layout of the widget and a view of the resulting output:
32
+
33
+
34
+ ```python
35
+ slider = pnw.IntSlider(name='time', start=0, end=10)
36
+
37
+ ds.air.interactive(width=800).isel(time=slider)
38
+ ```
39
+
40
+ You can also use widgets from the [ipywidgets](https://ipywidgets.readthedocs.io) library:
41
+
42
+
43
+ ```python
44
+ slider = ipw.IntSlider(description='time', min=0, max=10)
45
+
46
+ ds.air.interactive(width=800).isel(time=slider)
47
+ ```
48
+
49
+ Note that this works just as well for DataFrame objects whether they are Pandas, Dask or cuDF dataframes:
50
+
51
+
52
+ ```python
53
+ nrows = pn.widgets.IntSlider(start=1, end=100, value=10)
54
+
55
+ df.interactive(width=500).head(nrows)
56
+ ```
57
+
58
+ For Panel widgets, we can let .interactive automatically configure the widget, which is particularly convenient when working with `DiscreteSlider` widgets:
59
+
60
+
61
+ ```python
62
+ ds.air.interactive(width=800).sel(time=pnw.DiscreteSlider)
63
+ ```
64
+
65
+ ## Functions as inputs
66
+
67
+ In some cases your starting point for your interactive pipeline may not simply be a DataFrame or xarray Dataset but a function that fetches some data or applies some initial processing on your data. In such a case you can use the `hvplot.bind` function to bind static AND dynamic arguments to your function. Binding dynamic arguments such as a widget or parameter means that whenever the widget/parameter value changes the output of the function will change as well. This makes it possible to construct functions as the input to your interactive pipeline that themselves represent some data pipeline.
68
+
69
+ In the example below we will explicitly declare a `Select` widget to select between multiple stock tickers and a function that loads dataframes containing data for each of those stocks. Using the `hvplot.bind` function we then bind the `ticker` select widget to the `ticker` argument of the `stock_df` function and call `.interactive` on the resulting bound function:
70
+
71
+
72
+ ```python
73
+ from bokeh import sampledata
74
+
75
+ ticker = pn.widgets.Select(options=['AAPL', 'IBM', 'GOOG', 'MSFT'], name='Ticker')
76
+
77
+ def stock_df(ticker):
78
+ df = pd.DataFrame(getattr(sampledata.stocks, ticker))
79
+ df['date'] = pd.to_datetime(df.date)
80
+ return df
81
+
82
+ stock_dfi = hvplot.bind(stock_df, ticker).interactive(width=600)
83
+
84
+ stock_dfi.head(10)
85
+ ```
86
+
87
+ As you can see this `interactive` component behaves just like any other, allowing us to chain `.head` on it and updating when the `ticker` widget changes.
88
+
89
+ Just like any other `interactive` component you may also chain it further:
90
+
91
+
92
+ ```python
93
+ ticker = pn.widgets.Select(options=['AAPL', 'IBM', 'GOOG', 'MSFT'], name='Ticker')
94
+
95
+ def stock_df(ticker):
96
+ df = pd.DataFrame(getattr(sampledata.stocks, ticker))
97
+ df['date'] = pd.to_datetime(df.date)
98
+ return df
99
+
100
+ stock_dfi = hvplot.bind(stock_df, ticker).interactive()
101
+
102
+ dt_range = pn.widgets.DateRangeSlider(start=df.date.iloc[-1000], end=df.date.max(), value=(df.date.iloc[-100], df.date.max()))
103
+
104
+ stock_dfi[(stock_dfi.date>=dt_range.param.value_start) & (stock_dfi.date<=dt_range.param.value_end)].hvplot(kind='ohlc', grid=True, title=ticker)
105
+ ```
106
+
107
+ ## Docstrings
108
+
109
+ When accessing a method on the `.interactive` accessor it will transparently mirror the docstring of the equivalent method in the underlying library being wrapped:
110
+
111
+
112
+ ```python
113
+ print(ds.air.interactive.isel.__doc__)
114
+ ```
115
+
116
+ ## Plotting
117
+
118
+ One of the most useful aspects of the .interactive API is to feed the output of chained method calls into a plot.
119
+
120
+ ### Matplotlib
121
+
122
+ The output can be almost anything, such as the HTML repr (above) or a matplotlib plot:
123
+
124
+
125
+ ```python
126
+ ds.air.interactive.sel(time=pnw.DiscreteSlider).plot()
127
+ ```
128
+
129
+ If we like, we can animate the output with a `Player` widget, and customize the location of the widget using the `loc` keyword argument to `.interactive`:
130
+
131
+
132
+ ```python
133
+ time = pnw.Player(name='time', start=0, end=10, loop_policy='loop', interval=100)
134
+
135
+ ds.air.interactive(loc='bottom').isel(time=time).plot()
136
+ ```
137
+
138
+ ### hvPlot
139
+
140
+ We can also make use of the `.hvplot` method to get fully interactive Bokeh-based plots:
141
+
142
+
143
+ ```python
144
+ slider = pnw.FloatSlider(name='quantile', start=0, end=1)
145
+
146
+ ds.air.interactive.quantile(slider, dim='time').hvplot(data_aspect=1)
147
+ ```
148
+
149
+ You can chain any number of methods, with as many widgets controlling steps in this pipeline as you wish:
150
+
151
+
152
+ ```python
153
+ q = pnw.FloatSlider(name='quantile', start=0, end=1)
154
+
155
+ (ds.air.interactive(loc='left')
156
+ .sel(time=pnw.DiscreteSlider)
157
+ .quantile(q=q, dim='lon')
158
+ .hvplot(aspect=1))
159
+ ```
160
+
161
+ We can also use a `RangeSlider` to select a slice and compute the mean over that range instead of selecting a specific time:
162
+
163
+
164
+ ```python
165
+ range_slider = pnw.IntRangeSlider
166
+
167
+ (ds.air.interactive
168
+ .isel(time=range_slider)
169
+ .mean('time')
170
+ .hvplot())
171
+ ```
172
+
173
+ `.interactive` supports arbitrary chains of method calls, including anything that is supported by your data object. For instance, you can even convert your xarray object into a dataframe using `.to_dataframe`, then call pandas methods:
174
+
175
+
176
+ ```python
177
+ ds.air.interactive.sel(lat=pnw.DiscreteSlider).to_dataframe().groupby('time').mean().hvplot('time', 'air')
178
+ ```
179
+
180
+ ## Operators
181
+
182
+ You can further transform your output, if desired, by applying math operators on the interactive object:
183
+
184
+
185
+ ```python
186
+ slider = pnw.IntSlider(name='time', start=0, end=10)
187
+ baseline = ds.air.mean().item()
188
+ baseline
189
+ ```
190
+
191
+
192
+ ```python
193
+ ds.air.interactive(width=800).isel(time=slider).mean().item() - baseline
194
+ ```
195
+
196
+ You can even do math with a widget:
197
+
198
+
199
+ ```python
200
+ slider = pnw.IntSlider(name='time', start=0, end=10)
201
+ offset = pnw.IntSlider(name='offset', start=0, end=500)
202
+
203
+ ds.air.interactive.isel(time=slider).mean().item() + offset
204
+ ```
205
+
206
+ Math operators work with array data as well, such as the time-averaged value of each array value:
207
+
208
+
209
+ ```python
210
+ diff = ds.air.interactive.sel(time=pnw.DiscreteSlider) - ds.air.mean('time')
211
+ kind = pnw.Select(options=['contour', 'contourf', 'image'])
212
+
213
+ diff.hvplot(cmap='RdBu_r', clim=(-20, 20), kind=kind)
214
+ ```
215
+
216
+ If you want more control over the layout, you can use any of the features from [Panel](https://panel.holoviz.org). In this case, `interactive.panel()` (or `interactive.output()`) make sure you display the interactive plot only, with the widgets explicitly declared by `interactive.widgets()`:
217
+
218
+
219
+ ```python
220
+ diff = ds.air.interactive.sel(time=pnw.DiscreteSlider) - ds.air.mean('time')
221
+ kind = pnw.Select(options=['contourf', 'contour', 'image'], value='image')
222
+ interactive = diff.hvplot(cmap='RdBu_r', clim=(-20, 20), kind=kind)
223
+
224
+ pn.Column(
225
+ pn.Row(
226
+ pn.panel("https://hvplot.holoviz.org/assets/hvplot-wm.png", width=100),
227
+ pn.Spacer(width=20),
228
+ pn.Column(
229
+ pn.panel("## Select a time and type of plot", width=400),
230
+ interactive.widgets()
231
+ ),
232
+ pn.panel("https://panel.holoviz.org/_static/logo_stacked.png", width=100)
233
+ ),
234
+ interactive.panel()
235
+ ).servable()
236
+ ```
237
+
238
+ As you can see, the `.interactive` functionality makes it simple to work interactively with your data, letting you use widgets about as easily as any other method argument! See the Panel or ipwidgets docs for the various widgets and other functionality available.
hvplot_docs/Introduction.md ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The PyData ecosystem has a number of core Python data containers that allow users to work with a wide array of datatypes, including:
2
+
3
+ * [Pandas](https://pandas.pydata.org): DataFrame, Series (columnar/tabular data)
4
+ * [Rapids cuDF](https://docs.rapids.ai/api/cudf/stable/): GPU DataFrame, Series (columnar/tabular data)
5
+ * [Polars](https://www.pola.rs/): Polars is a fast DataFrame library/in-memory query engine (columnar/tabular data)
6
+ * [Dask](https://www.dask.org): DataFrame, Series (distributed/out of core arrays and columnar data)
7
+ * [XArray](https://xarray.pydata.org): Dataset, DataArray (labelled multidimensional arrays)
8
+ * [Streamz](https://streamz.readthedocs.io): DataFrame(s), Series(s) (streaming columnar data)
9
+ * [Intake](https://github.com/ContinuumIO/intake): DataSource (data catalogues)
10
+ * [GeoPandas](https://geopandas.org): GeoDataFrame (geometry data)
11
+ * [NetworkX](https://networkx.github.io/documentation/stable/): Graph (network graphs)
12
+
13
+ Many of these libraries have the concept of a high-level plotting API that lets a user generate common plot types very easily. The native plotting APIs are generally built on [Matplotlib](https://matplotlib.org), which provides a solid foundation, but means that users miss out the benefits of modern, interactive plotting libraries for the web like [Bokeh](https://bokeh.pydata.org) and [HoloViews](https://holoviews.org).
14
+
15
+ **hvPlot** provides a high-level plotting API built on HoloViews that provides a general and consistent API for plotting data in all the formats mentioned above.
16
+
17
+ As a first simple illustration of using hvPlot, let's create a small set of random data in Pandas to explore:
18
+
19
+
20
+ ```python
21
+ import numpy as np
22
+ import pandas as pd
23
+
24
+ index = pd.date_range('1/1/2000', periods=1000)
25
+ df = pd.DataFrame(np.random.randn(1000, 4), index=index, columns=list('ABCD')).cumsum()
26
+
27
+ df.head()
28
+ ```
29
+
30
+ ## Pandas default .plot()
31
+
32
+ Pandas provides Matplotlib-based plotting by default, using the `.plot()` method:
33
+
34
+
35
+ ```python
36
+ %matplotlib inline
37
+
38
+ df.plot();
39
+ ```
40
+
41
+ The result is a PNG image that displays easily, but is otherwise static.
42
+
43
+ ## Switching Pandas backend
44
+
45
+ To allow using hvPlot directly with Pandas we have to import `hvplot.pandas` and swap the Pandas backend with:
46
+
47
+
48
+ ```python
49
+ import hvplot.pandas # noqa
50
+
51
+ pd.options.plotting.backend = 'holoviews'
52
+ ```
53
+
54
+ **NOTE:** This requires a recent version of pandas (later than 0.25.0), see the [Pandas API](Pandas_API.ipynb) for more details.
55
+
56
+
57
+ ```python
58
+ df.plot()
59
+ ```
60
+
61
+ ## .hvplot()
62
+
63
+ If we instead change `%matplotlib inline` to `import hvplot.pandas` and use the ``df.hvplot`` method, it will now display an interactively explorable [Bokeh](https://bokeh.pydata.org) plot with panning, zooming, hovering, and clickable/selectable legends:
64
+
65
+
66
+ ```python
67
+ df.hvplot()
68
+ ```
69
+
70
+ This interactive plot makes it much easier to explore the properties of the data, without having to write code to select ranges, columns, or data values manually. Note that while pandas, dask and xarray all use the `.hvplot` method, `intake` uses hvPlot as its main plotting API, which means that is available using `.plot()`.
71
+
72
+ ## hvPlot native API
73
+
74
+ For the plot above, hvPlot dynamically added the Pandas `.hvplot()` method, so that you can use the same syntax as with the Pandas default plotting. If you prefer to be more explicit, you can instead work directly with hvPlot objects:
75
+
76
+
77
+ ```python
78
+ from hvplot import hvPlot
79
+ hvplot.extension('bokeh')
80
+
81
+ plot = hvPlot(df)
82
+ plot(y=['A', 'B', 'C', 'D'])
83
+ ```
84
+
85
+ ## Switching the plotting extension to Matplotlib or Plotly
86
+
87
+ While the default plotting extension of hvPlot is [Bokeh](https://bokeh.pydata.org), it is possible to load either Matplotlib or Plotly with `.extension()` and later switch from a plotting library to another with `.output()`. More information about working with multiple plotting backends can be found in the [plotting extensions guide](Plotting_Extensions.ipynb).
88
+
89
+
90
+ ```python
91
+ hvplot.extension('matplotlib')
92
+
93
+ df.hvplot(rot=30)
94
+ ```
95
+
96
+ ## Getting help
97
+
98
+ When working inside IPython or the Jupyter notebook hvplot methods will automatically complete valid keywords, e.g. pressing tab after declaring the plot type will provide all valid keywords and the docstring:
99
+
100
+ ```python
101
+ df.hvplot.line(<TAB>
102
+ ```
103
+
104
+ Outside an interactive environment ``hvplot.help`` will bring up information providing the ``kind`` of plot, e.g.:
105
+
106
+ ```python
107
+ hvplot.help('line')
108
+ ```
109
+
110
+ For more detail on the available options see the [Customization](Customization.ipynb) user guide.
111
+
112
+
113
+ ## Next steps
114
+
115
+ Now that you can see how hvPlot is used, let's jump straight in and discover some of the more powerful things we can do with it in the [Plotting](Plotting.ipynb) section.
hvplot_docs/Linked_Brushing.md ADDED
The diff for this file is too large to render. See raw diff
 
hvplot_docs/Linking_Plots.md ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import numpy as np
3
+ import holoviews as hv
4
+ from holoviews import opts
5
+
6
+ hv.extension('bokeh')
7
+ ```
8
+
9
+ When working with the bokeh backend in HoloViews complex interactivity can be achieved using very little code, whether that is shared axes, which zoom and pan together or shared datasources, which allow for linked cross-filtering. Separately it is possible to create custom interactions by attaching LinkedStreams to a plot and thereby triggering events on interactions with the plot. The Streams based interactivity affords a lot of flexibility to declare custom interactivity on a plot, however it always requires a live Python kernel to be connected either via the notebook or bokeh server. The ``Link`` classes described in this user guide however allow declaring interactions which do not require a live server, opening up the possibility of declaring complex interactions in a plot that can be exported to a static HTML file.
10
+
11
+ ## What is a ``Link``?
12
+
13
+ A ``Link`` defines some connection between a source and target object in their visualization. It is quite similar to a ``Stream`` as it allows defining callbacks in response to some change or event on the source object, however, unlike a Stream, it does not transfer data between the browser and a Python process. Instead a ``Link`` directly causes some action to occur on the ``target``, for JS based backends this usually means that a corresponding JS callback will effect some change on the target in response to a change on the source.
14
+
15
+ One of the simplest examples of a ``Link`` is the ``DataLink`` which links the data from two sources as long as they match in length, e.g. below we create two elements with data of the same length. By declaring a ``DataLink`` between the two we can ensure they are linked and can be selected together:
16
+
17
+
18
+ ```python
19
+ from holoviews.plotting.links import DataLink
20
+
21
+ scatter1 = hv.Scatter(np.arange(100))
22
+ scatter2 = hv.Scatter(np.arange(100)[::-1], 'x2', 'y2')
23
+
24
+ dlink = DataLink(scatter1, scatter2)
25
+
26
+ (scatter1 + scatter2).opts(
27
+ opts.Scatter(tools=['box_select', 'lasso_select']))
28
+ ```
29
+
30
+ If we want to display the elements subsequently without linking them we can call the ``unlink`` method:
31
+
32
+
33
+ ```python
34
+ dlink.unlink()
35
+
36
+ (scatter1 + scatter2)
37
+ ```
38
+
39
+ Another example of a link is the ``RangeToolLink`` which adds a RangeTool to the ``source`` plot which is linked to the axis range on the ``target`` plot. In this way the source plot can be used as an overview of the full data while the target plot provides a more detailed view of a subset of the data:
40
+
41
+
42
+ ```python
43
+ from holoviews.plotting.links import RangeToolLink
44
+
45
+ data = np.random.randn(1000).cumsum()
46
+
47
+ source = hv.Curve(data).opts(width=800, height=125, axiswise=True, default_tools=[])
48
+ target = hv.Curve(data).opts(width=800, labelled=['y'], toolbar=None)
49
+
50
+ rtlink = RangeToolLink(source, target)
51
+
52
+ (target + source).opts(merge_tools=False).cols(1)
53
+ ```
54
+
55
+ ## Advanced: Writing a ``Link``
56
+
57
+ A ``Link`` consists of two components the ``Link`` itself and a ``LinkCallback`` which provides the actual implementation behind the ``Link``. In order to demonstrate writing a ``Link`` we'll start with a fairly straightforward example, linking an ``HLine`` or ``VLine`` to the mean value of a selection on a ``Scatter`` element. To express this we declare a ``MeanLineLink`` class subclassing from the ``Link`` baseclass and declare ``ClassSelector`` parameters for the ``source`` and ``target`` with the appropriate types to perform some basic validation. Additionally we declare a ``column`` parameter to specify which column to compute the mean on.
58
+
59
+
60
+ ```python
61
+ import param
62
+ from holoviews.plotting.links import Link
63
+
64
+ class MeanLineLink(Link):
65
+
66
+ column = param.String(default='x', doc="""
67
+ The column to compute the mean on.""")
68
+
69
+ _requires_target = True
70
+ ```
71
+
72
+ Now we have the ``Link`` class we need to write the implementation in the form of a ``LinkCallback``, which in the case of bokeh will be translated into a [``CustomJS`` callback](https://bokeh.pydata.org/en/latest/docs/user_guide/interaction/callbacks.html#userguide-interaction-jscallbacks). A ``LinkCallback`` should declare the ``source_model`` we want to listen to events on and a ``target_model``, declaring which model should be altered in response. To find out which models we can attach the ``Link`` to we can create a ``Plot`` instance and look at the ``plot.handles``, e.g. here we create a ``ScatterPlot`` and can see it has a 'cds', which represents the ``ColumnDataSource``.
73
+
74
+
75
+ ```python
76
+ renderer = hv.renderer('bokeh')
77
+
78
+ plot = renderer.get_plot(hv.Scatter([]))
79
+
80
+ plot.handles.keys()
81
+ ```
82
+
83
+ In this case we are interested in the 'cds' handle, but we still have to tell it which events should trigger the callback. Bokeh callbacks can be grouped into two types, model property changes and events. For more detail on these two types of callbacks see the [Bokeh user guide](https://bokeh.pydata.org/en/latest/docs/user_guide/interaction/callbacks.html#userguide-interaction-jscallbacks).
84
+
85
+ For this example we want to respond to changes to the ``ColumnDataSource.selected`` property. We can declare this in the ``on_source_changes`` class attribute on our callback. So now that we have declared which model we want to listen to events on and which events we want to listen to, we have to declare the model on the target we want to change in response.
86
+
87
+ We can once again look at the handles on the plot corresponding to the ``HLine`` element:
88
+
89
+
90
+ ```python
91
+ plot = renderer.get_plot(hv.HLine(0))
92
+ plot.handles.keys()
93
+ ```
94
+
95
+ We now want to change the ``glyph``, which defines the position of the ``HLine``, so we declare the ``target_model`` as ``'glyph'``. Having defined both the source and target model and the events we can finally start writing the JS callback that should be triggered. To declare it we simply define the ``source_code`` class attribute. To understand how to write this code we need to understand how the source and target models, we have declared, can be referenced from within the callback.
96
+
97
+ The ``source_model`` will be made available by prefixing it with ``source_``, while the target model is made available with the prefix ``target_``. This means that the ``ColumnDataSource`` on the ``source`` can be referenced as ``source_source``, while the glyph on the target can be referenced as ``target_glyph``.
98
+
99
+ Finally, any parameters other than the ``source`` and ``target`` on the ``Link`` will also be made available inside the callback, which means we can reference the appropriate ``column`` in the ``ColumnDataSource`` to compute the mean value along a particular axis.
100
+
101
+ Once we know how to reference the bokeh models and ``Link`` parameters we can access their properties to compute the mean value of the current selection on the source ``ColumnDataSource`` and set the ``target_glyph.position`` to that value.
102
+
103
+ A ``LinkCallback`` may also define a validate method to validate that the Link parameters and plots are compatible, e.g. in this case we can validate that the ``column`` is actually present in the source_plot ``ColumnDataSource``.
104
+
105
+
106
+ ```python
107
+ from holoviews.plotting.bokeh import LinkCallback
108
+
109
+ class MeanLineCallback(LinkCallback):
110
+
111
+ source_model = 'selected'
112
+ source_handles = ['cds']
113
+ on_source_changes = ['indices']
114
+
115
+ target_model = 'glyph'
116
+
117
+ source_code = """
118
+ var inds = source_selected.indices
119
+ var d = source_cds.data
120
+ var vm = 0
121
+ if (inds.length == 0)
122
+ return
123
+ for (var i = 0; i < inds.length; i++)
124
+ vm += d[column][inds[i]]
125
+ vm /= inds.length
126
+ target_glyph.location = vm
127
+ """
128
+
129
+ def validate(self):
130
+ assert self.link.column in self.source_plot.handles['cds'].data
131
+ ```
132
+
133
+ Finally we need to register the ``MeanLineLinkCallback`` with the ``MeanLineLink`` using the ``register_callback`` classmethod:
134
+
135
+
136
+ ```python
137
+ MeanLineLink.register_callback('bokeh', MeanLineCallback)
138
+ ```
139
+
140
+ Now the newly declared Link is ready to use, we'll create a ``Scatter`` element along with an ``HLine`` and ``VLine`` element and link each one:
141
+
142
+
143
+ ```python
144
+ options = opts.Scatter(
145
+ selection_fill_color='firebrick', alpha=0.4, line_color='black', size=8,
146
+ tools=['lasso_select', 'box_select'], width=500, height=500,
147
+ active_tools=['lasso_select']
148
+ )
149
+ scatter = hv.Scatter(np.random.randn(500, 2)).opts(options)
150
+ vline = hv.VLine(scatter['x'].mean()).opts(color='black')
151
+ hline = hv.HLine(scatter['y'].mean()).opts(color='black')
152
+
153
+ MeanLineLink(scatter, vline, column='x')
154
+ MeanLineLink(scatter, hline, column='y')
155
+
156
+ scatter * hline * vline
157
+ ```
158
+
159
+ Using the 'box_select' and 'lasso_select' tools will now update the position of the HLine and VLine.
hvplot_docs/NetworkX.md ADDED
@@ -0,0 +1,552 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The hvPlot NetworkX plotting API is meant as a drop-in replacement for the ``networkx.draw`` methods. In most cases the existing code will work as is or with minor modifications, returning a HoloViews object rendering an interactive bokeh plot, equivalent to the matplotlib plot the standard API constructs. First let us import the plotting interface and give it the canonical name ``hvnx``:
2
+
3
+
4
+ ```python
5
+ import hvplot.networkx as hvnx
6
+
7
+ import networkx as nx
8
+ import holoviews as hv
9
+ ```
10
+
11
+ In this user guide we will follow along with many of the examples in the [NetworkX tutorial](https://networkx.github.io/documentation/stable/tutorial.html#drawing-graphs) on drawing graphs.
12
+
13
+ The ``hxnx`` namespace provides all the same plotting functions as ``nx``, this means in most cases one can simply be swapped for the other. This also includes most keywords used to customize the plots. The main difference is in the way multiple plots are composited, like all other hvPlot APIs the networkX functions returns HoloViews objects which can be composited using ``+`` and ``*`` operations:
14
+
15
+
16
+ ```python
17
+ G = nx.petersen_graph()
18
+
19
+ spring = hvnx.draw(G, with_labels=True)
20
+ shell = hvnx.draw_shell(G, nlist=[range(5, 10), range(5)], with_labels=True, font_weight='bold')
21
+
22
+ spring + shell
23
+ ```
24
+
25
+
26
+ ```python
27
+ H = nx.triangular_lattice_graph(1, 20)
28
+ hvnx.draw_planar(H, node_color='green', edge_color='brown')
29
+ ```
30
+
31
+ The most common ``layout`` functions have dedicated drawing methods such as the ``draw_shell`` function above, which automatically computes the node positions.
32
+
33
+ However layout algorithms are not necessarily deterministic, so if we want to plot and overlay subsets of either the nodes or edges using the ``nodelist`` and ``edgelist`` keywords the node positions should be computed ahead of time and passed in explicitly:
34
+
35
+
36
+ ```python
37
+ pos = nx.layout.spring_layout(G)
38
+
39
+ hvnx.draw(G, pos, nodelist=[0, 1, 2, 3, 4], node_color='blue') *\
40
+ hvnx.draw_networkx_nodes(G, pos, nodelist=[5, 6, 7, 8, 9], node_color='green')
41
+ ```
42
+
43
+ The ``hvnx`` namespace also makes ``save`` and ``show utilities available to save the plot to HTML or PNG files or display it in a separate browser window when working in a standard Python interpreter.
44
+
45
+
46
+ ```python
47
+ G = nx.dodecahedral_graph()
48
+
49
+ shells = [[2, 3, 4, 5, 6], [8, 1, 0, 19, 18, 17, 16, 15, 14, 7], [9, 10, 11, 12, 13]]
50
+ shell = hvnx.draw_shell(G, nlist=shells)
51
+
52
+ pos = nx.nx_agraph.graphviz_layout(G)
53
+ graphviz = hvnx.draw(G, pos=pos)
54
+
55
+ layout = shell + graphviz
56
+
57
+ hvnx.save(layout, 'graph_layout.png')
58
+ ```
59
+
60
+ #### Styling Graphs
61
+
62
+ The full set of options which are inherited from networkx's API are listed in the ``hxnx.draw()`` docstring. Using these the more common styling of nodes and edges can easily be altered through the common set of options that are inherited from networkx. In addition common HoloViews options to control the size of the plots, axes and styling are also supported. Finally, some ``layout`` functions also accept special keyword arguments such as the ``nlist`` argument for the shell layout which specifies the shells.
63
+
64
+
65
+ ```python
66
+ options = {
67
+ 'node_color': 'black',
68
+ 'node_size': 100,
69
+ 'edge_width': 3,
70
+ 'width': 300,
71
+ 'height': 300
72
+ }
73
+
74
+ random = hvnx.draw_random(G, **options)
75
+ circular = hvnx.draw_circular(G, **options)
76
+ spectral = hvnx.draw_spectral(G, **options)
77
+ shell = hvnx.draw_shell(G, nlist=[range(5,10), range(5)], **options)
78
+
79
+ (random + circular + spectral + shell).cols(2)
80
+ ```
81
+
82
+ In addition to being able to set scalar style values hvPlot also supports the HoloViews concept of [style mapping](https://holoviews.org/user_guide/Style_Mapping.html#styling-mapping), which uses so called ``dim`` transforms to map attributes of the graph nodes and edges to vary the visual attributes of the plot. For example we might construct a graph with edge weights and node sizes as attributes. The plotting function will extract these attributes which means they can be used to scale visual properties of the plot such as the ``edge_width``, ``edge_color`` or ``node_size``:
83
+
84
+
85
+ ```python
86
+ G = nx.Graph()
87
+
88
+ G.add_edge('a', 'b', weight=0.6)
89
+ G.add_edge('a', 'c', weight=0.2)
90
+ G.add_edge('c', 'd', weight=0.1)
91
+ G.add_edge('c', 'e', weight=0.7)
92
+ G.add_edge('c', 'f', weight=0.9)
93
+ G.add_edge('a', 'd', weight=0.3)
94
+
95
+ G.add_node('a', size=20)
96
+ G.add_node('b', size=10)
97
+ G.add_node('c', size=12)
98
+ G.add_node('d', size=5)
99
+ G.add_node('e', size=8)
100
+ G.add_node('f', size=3)
101
+
102
+ pos = nx.spring_layout(G) # positions for all nodes
103
+
104
+ hvnx.draw(G, pos, edge_color='weight', edge_cmap='viridis',
105
+ edge_width=hv.dim('weight')*10, node_size=hv.dim('size')*20)
106
+ ```
107
+
108
+ The full set of options that are supported can be accessed on the ``hvnx.draw`` function (note this does not include some bokeh specific option to control the styling of selection, nonselection and hover nodes and edges which may also be supplied and follow a pattern like ``hover_node_fill_color`` or ``selection_edge_line_alpha``).
109
+
110
+ For reference here is the docstring listing the main supported option:
111
+
112
+
113
+ ```python
114
+ print(hvnx.draw.__doc__)
115
+ ```
116
+
117
+ The main difference to the networkx.draw API are a few options which are not supported (such as `font_weight` and `arrowsize`) and the renaming of `width` (which controls the edge line width) to ``edge_width`` since `width` and `height` are reserved for defining the screen dimensions of the plot.
118
+
119
+ ## Examples
120
+
121
+ To demonstrate that the API works almost identically this section reproduces various examples from the NetworkX documentation.
122
+
123
+ ### Plot properties
124
+
125
+ Compute some network properties for the lollipop graph.
126
+
127
+ URL: https://networkx.github.io/documentation/stable/auto_examples/basic/plot_properties.html
128
+
129
+
130
+ ```python
131
+ # Copyright (C) 2004-2018 by
132
+ # Aric Hagberg <hagberg@lanl.gov>
133
+ # Dan Schult <dschult@colgate.edu>
134
+ # Pieter Swart <swart@lanl.gov>
135
+ # All rights reserved.
136
+ # BSD license.
137
+
138
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
139
+
140
+ G = nx.lollipop_graph(4, 6)
141
+
142
+ pathlengths = []
143
+
144
+ print("source vertex {target:length, }")
145
+ for v in G.nodes():
146
+ spl = dict(nx.single_source_shortest_path_length(G, v))
147
+ print('{} {} '.format(v, spl))
148
+ for p in spl:
149
+ pathlengths.append(spl[p])
150
+
151
+ print('')
152
+ print("average shortest path length %s" % (sum(pathlengths) / len(pathlengths)))
153
+
154
+ # histogram of path lengths
155
+ dist = {}
156
+ for p in pathlengths:
157
+ if p in dist:
158
+ dist[p] += 1
159
+ else:
160
+ dist[p] = 1
161
+
162
+ print('')
163
+ print("length #paths")
164
+ verts = dist.keys()
165
+ for d in sorted(verts):
166
+ print('%s %d' % (d, dist[d]))
167
+
168
+ print("radius: %d" % nx.radius(G))
169
+ print("diameter: %d" % nx.diameter(G))
170
+ print("eccentricity: %s" % nx.eccentricity(G))
171
+ print("center: %s" % nx.center(G))
172
+ print("periphery: %s" % nx.periphery(G))
173
+ print("density: %s" % nx.density(G))
174
+
175
+ hvnx.draw(G, with_labels=True)
176
+ ```
177
+
178
+ ### Simple Path
179
+
180
+ Draw a graph with hvPlot.
181
+
182
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_simple_path.html
183
+
184
+
185
+ ```python
186
+ G = nx.path_graph(8)
187
+ hvnx.draw(G)
188
+ ```
189
+
190
+ ### Node colormap
191
+
192
+ Draw a graph with hvPlot, color by degree.
193
+
194
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_node_colormap.html
195
+
196
+
197
+ ```python
198
+ # Author: Aric Hagberg (hagberg@lanl.gov)
199
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
200
+
201
+ G = nx.cycle_graph(24)
202
+ pos = nx.spring_layout(G, iterations=200)
203
+
204
+ # Preferred API
205
+ # hvnx.draw(G, pos, node_color='index', node_size=500, cmap='Blues')
206
+
207
+ # Original code
208
+ hvnx.draw(G, pos, node_color=range(24), node_size=500, cmap='Blues')
209
+ ```
210
+
211
+ ### Edge Colormap
212
+
213
+ Draw a graph with hvPlot, color edges.
214
+
215
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_edge_colormap.html
216
+
217
+
218
+ ```python
219
+ # Author: Aric Hagberg (hagberg@lanl.gov)
220
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
221
+
222
+ G = nx.star_graph(20)
223
+ pos = nx.spring_layout(G)
224
+ colors = range(20)
225
+ hvnx.draw(G, pos, node_color='#A0CBE2', edge_color=colors,
226
+ edge_width=4, edge_cmap='Blues', with_labels=False)
227
+ ```
228
+
229
+ ### House With Colors
230
+
231
+ Draw a graph with hvPlot.
232
+
233
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_house_with_colors.html
234
+
235
+
236
+ ```python
237
+ # Author: Aric Hagberg (hagberg@lanl.gov)
238
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
239
+
240
+ G = nx.house_graph()
241
+ # explicitly set positions
242
+ pos = {0: (0, 0),
243
+ 1: (1, 0),
244
+ 2: (0, 1),
245
+ 3: (1, 1),
246
+ 4: (0.5, 2.0)}
247
+
248
+ hvnx.draw_networkx_nodes(G, pos, node_size=2000, nodelist=[4], padding=0.2) *\
249
+ hvnx.draw_networkx_nodes(G, pos, node_size=3000, nodelist=[0, 1, 2, 3], node_color='black') *\
250
+ hvnx.draw_networkx_edges(G, pos, alpha=0.5, width=6, xaxis=None, yaxis=None)
251
+ ```
252
+
253
+ ### Circular Tree
254
+
255
+ URL: https://networkx.org/documentation/stable/auto_examples/graphviz_layout/plot_circular_tree.html
256
+
257
+
258
+ ```python
259
+ try:
260
+ import pygraphviz # noqa
261
+ from networkx.drawing.nx_agraph import graphviz_layout
262
+ except ImportError:
263
+ try:
264
+ import pydot # noqa
265
+ from networkx.drawing.nx_pydot import graphviz_layout
266
+ except ImportError:
267
+ raise ImportError("This example needs Graphviz and either "
268
+ "PyGraphviz or pydot")
269
+
270
+ G = nx.balanced_tree(3, 5)
271
+ pos = graphviz_layout(G, prog='twopi', args='')
272
+ hvnx.draw(G, pos, node_size=20, alpha=0.5, node_color="blue", with_labels=False, width=600, height=600)
273
+ ```
274
+
275
+ ### Spectral Embedding
276
+
277
+ The spectral layout positions the nodes of the graph based on the eigenvectors of the graph Laplacian L=D−A, where A is the adjacency matrix and D is the degree matrix of the graph. By default, the spectral layout will embed the graph in two dimensions (you can embed your graph in other dimensions using the dim argument to either draw_spectral() or spectral_layout()).
278
+
279
+ When the edges of the graph represent similarity between the incident nodes, the spectral embedding will place highly similar nodes closer to one another than nodes which are less similar.
280
+
281
+ This is particularly striking when you spectrally embed a grid graph. In the full grid graph, the nodes in the center of the graph are pulled apart more than nodes on the periphery. As you remove internal nodes, this effect increases.
282
+
283
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_spectral_grid.html
284
+
285
+
286
+ ```python
287
+ options = {
288
+ 'node_size': 100,
289
+ 'width': 250, 'height': 250
290
+ }
291
+
292
+ G = nx.grid_2d_graph(6, 6)
293
+ spectral1 = hvnx.draw_spectral(G, **options)
294
+
295
+ G.remove_edge((2, 2), (2, 3))
296
+ spectral2 = hvnx.draw_spectral(G, **options)
297
+
298
+ G.remove_edge((3, 2), (3, 3))
299
+ spectral3 = hvnx.draw_spectral(G, **options)
300
+
301
+ G.remove_edge((2, 2), (3, 2))
302
+ spectral4 = hvnx.draw_spectral(G, **options)
303
+
304
+ G.remove_edge((2, 3), (3, 3))
305
+ spectral5 = hvnx.draw_spectral(G, **options)
306
+
307
+ G.remove_edge((1, 2), (1, 3))
308
+ spectral6 = hvnx.draw_spectral(G, **options)
309
+
310
+ G.remove_edge((4, 2), (4, 3))
311
+ spectral7 = hvnx.draw_spectral(G, **options)
312
+
313
+ (hv.Empty() + spectral1 + hv.Empty() +
314
+ spectral2 + spectral3 + spectral4 +
315
+ spectral5 + spectral6 + spectral7).cols(3)
316
+ ```
317
+
318
+ ### Plot four grids
319
+
320
+ Draw a graph with hvPlot.
321
+
322
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_four_grids.html
323
+
324
+
325
+ ```python
326
+ # Author: Aric Hagberg (hagberg@lanl.gov)
327
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
328
+
329
+ # Copyright (C) 2004-2018
330
+ # Aric Hagberg <hagberg@lanl.gov>
331
+ # Dan Schult <dschult@colgate.edu>
332
+ # Pieter Swart <swart@lanl.gov>
333
+ # All rights reserved.
334
+ # BSD license.
335
+
336
+ G = nx.grid_2d_graph(4, 4) # 4x4 grid
337
+
338
+ pos = nx.spring_layout(G, iterations=100)
339
+
340
+ g1 = hvnx.draw(G, pos, font_size=8)
341
+
342
+ g2 = hvnx.draw(G, pos, node_color='black', node_size=0, with_labels=False)
343
+
344
+ g3 = hvnx.draw(G, pos, node_color='green', node_size=250, with_labels=False, edge_width=6)
345
+
346
+ H = G.to_directed()
347
+ g4 = hvnx.draw(H, pos, node_color='blue', node_size=20, with_labels=False)
348
+
349
+ (g1 + g2 + g3 + g4).cols(2)
350
+ ```
351
+
352
+ ### Ego Graph
353
+
354
+ Example using the NetworkX ego_graph() function to return the main egonet of the largest hub in a Barabási-Albert network.
355
+
356
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_ego_graph.html
357
+
358
+
359
+ ```python
360
+ # Author: Drew Conway (drew.conway@nyu.edu)
361
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
362
+
363
+ from operator import itemgetter
364
+
365
+ # Create a BA model graph
366
+ n = 1000
367
+ m = 2
368
+ G = nx.generators.barabasi_albert_graph(n, m)
369
+ # find node with largest degree
370
+ node_and_degree = G.degree()
371
+ (largest_hub, degree) = sorted(node_and_degree, key=itemgetter(1))[-1]
372
+ # Create ego graph of main hub
373
+ hub_ego = nx.ego_graph(G, largest_hub)
374
+ # Draw graph
375
+ pos = nx.spring_layout(hub_ego)
376
+ g = hvnx.draw(hub_ego, pos, node_color='blue', node_size=50, with_labels=False)
377
+ # Draw ego as large and red
378
+ gnodes = hvnx.draw_networkx_nodes(hub_ego, pos, nodelist=[largest_hub], node_size=300, node_color='red')
379
+
380
+ g * gnodes
381
+ ```
382
+
383
+ ### Random Geometric Graph
384
+
385
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_random_geometric_graph.html
386
+
387
+
388
+ ```python
389
+ G = nx.random_geometric_graph(200, 0.125)
390
+ # position is stored as node attribute data for random_geometric_graph
391
+ pos = nx.get_node_attributes(G, 'pos')
392
+
393
+ # find node near center (0.5,0.5)
394
+ dmin = 1
395
+ ncenter = 0
396
+ for n in pos:
397
+ x, y = pos[n]
398
+ d = (x - 0.5)**2 + (y - 0.5)**2
399
+ if d < dmin:
400
+ ncenter = n
401
+ dmin = d
402
+
403
+ # color by path length from node near center
404
+ p = nx.single_source_shortest_path_length(G, ncenter)
405
+
406
+ hvnx.draw_networkx_edges(G, pos, nodelist=[ncenter], alpha=0.4, width=600, height=600) *\
407
+ hvnx.draw_networkx_nodes(G, pos, nodelist=list(p.keys()),
408
+ node_size=80,
409
+ node_color=list(p.values()),
410
+ cmap='Reds_r')
411
+ ```
412
+
413
+ ### Weighted Graph
414
+
415
+ An example using Graph as a weighted network.
416
+
417
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_weighted_graph.html
418
+
419
+
420
+ ```python
421
+ # Author: Aric Hagberg (hagberg@lanl.gov)
422
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
423
+
424
+ import networkx as nx
425
+
426
+ G = nx.Graph()
427
+
428
+ G.add_edge('a', 'b', weight=0.6)
429
+ G.add_edge('a', 'c', weight=0.2)
430
+ G.add_edge('c', 'd', weight=0.1)
431
+ G.add_edge('c', 'e', weight=0.7)
432
+ G.add_edge('c', 'f', weight=0.9)
433
+ G.add_edge('a', 'd', weight=0.3)
434
+
435
+ elarge = [(u, v) for (u, v, attr) in G.edges(data=True) if attr['weight'] > 0.5]
436
+ esmall = [(u, v) for (u, v, attr) in G.edges(data=True) if attr['weight'] <= 0.5]
437
+
438
+ pos = nx.spring_layout(G) # positions for all nodes
439
+
440
+ # nodes
441
+ nodes = hvnx.draw_networkx_nodes(G, pos, node_size=700)
442
+
443
+ # edges
444
+ edges1 = hvnx.draw_networkx_edges(
445
+ G, pos, edgelist=elarge, edge_width=6)
446
+ edges2 = hvnx.draw_networkx_edges(
447
+ G, pos, edgelist=esmall, edge_width=6, alpha=0.5, edge_color='blue', style='dashed')
448
+ labels = hvnx.draw_networkx_labels(G, pos, font_size=20, font_family='sans-serif')
449
+
450
+ edges1 * edges2 * nodes * labels
451
+ ```
452
+
453
+ ### Directed Graph
454
+
455
+ Draw a graph with directed edges using a colormap and different node sizes.
456
+
457
+ Edges have different colors and alphas (opacity). Drawn using matplotlib.
458
+
459
+ URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_directed.html
460
+
461
+
462
+ ```python
463
+ # Author: Rodrigo Dorantes-Gilardi (rodgdor@gmail.com)
464
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
465
+
466
+ G = nx.generators.directed.random_k_out_graph(10, 3, 0.5)
467
+ pos = nx.layout.spring_layout(G)
468
+
469
+ node_sizes = [3 + 10 * i for i in range(len(G))]
470
+ M = G.number_of_edges()
471
+ edge_colors = range(2, M + 2)
472
+ edge_alphas = [(5 + i) / (M + 4) for i in range(M)]
473
+
474
+ nodes = hvnx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color='blue')
475
+ edges = hvnx.draw_networkx_edges(G, pos, node_size=node_sizes, arrowstyle='->',
476
+ arrowsize=10, edge_color=edge_colors,
477
+ edge_cmap='Blues', edge_width=2, colorbar=True)
478
+
479
+ nodes * edges
480
+ ```
481
+
482
+ ### Giant Component
483
+
484
+ This example illustrates the sudden appearance of a giant connected component in a binomial random graph.
485
+
486
+ https://networkx.org/documentation/stable/auto_examples/graphviz_layout/plot_giant_component.html
487
+
488
+
489
+ ```python
490
+ # Copyright (C) 2006-2018
491
+ # Aric Hagberg <hagberg@lanl.gov>
492
+ # Dan Schult <dschult@colgate.edu>
493
+ # Pieter Swart <swart@lanl.gov>
494
+ # All rights reserved.
495
+ # BSD license.
496
+
497
+ # Adapted by Philipp Rudiger <prudiger@anaconda.com>
498
+
499
+ import math
500
+
501
+ try:
502
+ import pygraphviz # noqa
503
+ from networkx.drawing.nx_agraph import graphviz_layout
504
+ layout = graphviz_layout
505
+ except ImportError:
506
+ try:
507
+ import pydot # noqa
508
+ from networkx.drawing.nx_pydot import graphviz_layout
509
+ layout = graphviz_layout
510
+ except ImportError:
511
+ print("PyGraphviz and pydot not found;\n"
512
+ "drawing with spring layout;\n"
513
+ "will be slow.")
514
+ layout = nx.spring_layout
515
+
516
+ n = 150 # 150 nodes
517
+ # p value at which giant component (of size log(n) nodes) is expected
518
+ p_giant = 1.0 / (n - 1)
519
+ # p value at which graph is expected to become completely connected
520
+ p_conn = math.log(n) / float(n)
521
+
522
+ # the following range of p values should be close to the threshold
523
+ pvals = [0.003, 0.006, 0.008, 0.015]
524
+
525
+ region = 220 # for pylab 2x2 subplot layout
526
+ plots = []
527
+ for p in pvals:
528
+ G = nx.binomial_graph(n, p)
529
+ pos = layout(G)
530
+ region += 1
531
+ g = hvnx.draw(G, pos, with_labels=False, node_size=15)
532
+ # identify largest connected component
533
+ Gcc = sorted([G.subgraph(c) for c in nx.connected_components(G)], key=len, reverse=True)
534
+ G0 = Gcc[0]
535
+ edges = hvnx.draw_networkx_edges(
536
+ G0, pos, with_labels=False, edge_color='red', edge_width=6.0)
537
+
538
+ # show other connected components
539
+ other_edges = []
540
+ for Gi in Gcc[1:]:
541
+ if len(Gi) > 1:
542
+ edge = hvnx.draw_networkx_edges(Gi, pos,
543
+ with_labels=False,
544
+ edge_color='red',
545
+ alpha=0.3,
546
+ edge_width=5.0
547
+ )
548
+ other_edges.append(edge)
549
+ plots.append((g*edges*hv.Overlay(other_edges)).relabel("p = %6.3f" % (p)))
550
+
551
+ hv.Layout(plots).cols(2)
552
+ ```
hvplot_docs/Network_Graphs.md ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import numpy as np
3
+ import pandas as pd
4
+ import holoviews as hv
5
+ import networkx as nx
6
+ from holoviews import opts
7
+
8
+ hv.extension('bokeh')
9
+
10
+ defaults = dict(width=400, height=400)
11
+ hv.opts.defaults(
12
+ opts.EdgePaths(**defaults), opts.Graph(**defaults), opts.Nodes(**defaults))
13
+ ```
14
+
15
+ Visualizing and working with network graphs is a common problem in many different disciplines. HoloViews provides the ability to represent and visualize graphs very simply and easily with facilities for interactively exploring the nodes and edges of the graph, especially using the bokeh plotting interface.
16
+
17
+ The ``Graph`` ``Element`` differs from other elements in HoloViews in that it consists of multiple sub-elements. The data of the ``Graph`` element itself are the abstract edges between the nodes. By default the element will automatically compute concrete ``x`` and ``y`` positions for the nodes and represent them using a ``Nodes`` element, which is stored on the Graph. The abstract edges and concrete node positions are sufficient to render the ``Graph`` by drawing straight-line edges between the nodes. In order to supply explicit edge paths we can also declare ``EdgePaths``, providing explicit coordinates for each edge to follow.
18
+
19
+ To summarize a ``Graph`` consists of three different components:
20
+
21
+ * The ``Graph`` itself holds the abstract edges stored as a table of node indices.
22
+ * The ``Nodes`` hold the concrete ``x`` and ``y`` positions of each node along with a node ``index``. The ``Nodes`` may also define any number of value dimensions, which can be revealed when hovering over the nodes or to color the nodes by.
23
+ * The ``EdgePaths`` can optionally be supplied to declare explicit node paths.
24
+
25
+ #### A simple Graph
26
+
27
+ Let's start by declaring a very simple graph connecting one node to all others. If we simply supply the abstract connectivity of the ``Graph``, it will automatically compute a layout for the nodes using the ``layout_nodes`` operation, which defaults to a circular layout:
28
+
29
+
30
+ ```python
31
+ # Declare abstract edges
32
+ N = 8
33
+ node_indices = np.arange(N, dtype=np.int32)
34
+ source = np.zeros(N, dtype=np.int32)
35
+ target = node_indices
36
+
37
+
38
+ simple_graph = hv.Graph(((source, target),))
39
+ simple_graph
40
+ ```
41
+
42
+ #### Accessing the nodes and edges
43
+
44
+ We can easily access the ``Nodes`` and ``EdgePaths`` on the ``Graph`` element using the corresponding properties:
45
+
46
+
47
+ ```python
48
+ simple_graph.nodes + simple_graph.edgepaths
49
+ ```
50
+
51
+ #### Displaying directed graphs
52
+
53
+ When specifying the graph edges the source and target node are listed in order, if the graph is actually a directed graph this may used to indicate the directionality of the graph. By setting ``directed=True`` as a plot option it is possible to indicate the directionality of each edge using an arrow:
54
+
55
+
56
+ ```python
57
+ simple_graph.relabel('Directed Graph').opts(directed=True, node_size=5, arrowhead_length=0.05)
58
+ ```
59
+
60
+ The length of the arrows can be set as an fraction of the overall graph extent using the ``arrowhead_length`` option.
61
+
62
+ #### Supplying explicit paths
63
+
64
+ Next we will extend this example by supplying explicit edges:
65
+
66
+
67
+ ```python
68
+ def bezier(start, end, control, steps=np.linspace(0, 1, 100)):
69
+ return (1-steps)**2*start + 2*(1-steps)*steps*control+steps**2*end
70
+
71
+ x, y = simple_graph.nodes.array([0, 1]).T
72
+
73
+ paths = []
74
+ for node_index in node_indices:
75
+ ex, ey = x[node_index], y[node_index]
76
+ paths.append(np.column_stack([bezier(x[0], ex, 0), bezier(y[0], ey, 0)]))
77
+
78
+ bezier_graph = hv.Graph(((source, target), (x, y, node_indices), paths))
79
+ bezier_graph
80
+ ```
81
+
82
+ ## Interactive features
83
+
84
+ #### Hover and selection policies
85
+
86
+ Thanks to Bokeh we can reveal more about the graph by hovering over the nodes and edges. The ``Graph`` element provides an ``inspection_policy`` and a ``selection_policy``, which define whether hovering and selection highlight edges associated with the selected node or nodes associated with the selected edge, these policies can be toggled by setting the policy to ``'nodes'`` (the default) and ``'edges'``.
87
+
88
+
89
+ ```python
90
+ bezier_graph.relabel('Edge Inspection').opts(inspection_policy='edges')
91
+ ```
92
+
93
+ In addition to changing the policy we can also change the colors used when hovering and selecting nodes:
94
+
95
+
96
+ ```python
97
+ bezier_graph.opts(
98
+ opts.Graph(inspection_policy='nodes', tools=['hover', 'box_select'],
99
+ edge_hover_line_color='green', node_hover_fill_color='red'))
100
+ ```
101
+
102
+ #### Additional information
103
+
104
+ We can also associate additional information with the nodes and edges of a graph. By constructing the ``Nodes`` explicitly we can declare additional value dimensions, which are revealed when hovering and/or can be mapped to the color by setting the ``color`` to the dimension name ('Weight'). We can also associate additional information with each edge by supplying a value dimension to the ``Graph`` itself, which we can map to various style options, e.g. by setting the ``edge_color`` and ``edge_line_width``.
105
+
106
+
107
+ ```python
108
+ node_labels = ['Output']+['Input']*(N-1)
109
+ np.random.seed(7)
110
+ edge_labels = np.random.rand(8)
111
+
112
+ nodes = hv.Nodes((x, y, node_indices, node_labels), vdims='Type')
113
+ graph = hv.Graph(((source, target, edge_labels), nodes, paths), vdims='Weight')
114
+
115
+ (graph + graph.opts(inspection_policy='edges', clone=True)).opts(
116
+ opts.Graph(node_color='Type', edge_color='Weight', cmap='Set1',
117
+ edge_cmap='viridis', edge_line_width=hv.dim('Weight')*10))
118
+ ```
119
+
120
+ If you want to supply additional node information without specifying explicit node positions you may pass in a ``Dataset`` object consisting of various value dimensions.
121
+
122
+
123
+ ```python
124
+ node_info = hv.Dataset(node_labels, vdims='Label')
125
+ hv.Graph(((source, target), node_info)).opts(node_color='Label', cmap='Set1')
126
+ ```
127
+
128
+ ## Working with NetworkX
129
+
130
+ NetworkX is a very useful library when working with network graphs and the Graph Element provides ways of importing a NetworkX Graph directly. Here we will load the Karate Club graph and use the ``circular_layout`` function provided by NetworkX to lay it out:
131
+
132
+
133
+ ```python
134
+ G = nx.karate_club_graph()
135
+ hv.Graph.from_networkx(G, nx.layout.circular_layout).opts(tools=['hover'])
136
+ ```
137
+
138
+ It is also possible to pass arguments to the NetworkX layout function as keywords to ``hv.Graph.from_networkx``, e.g. we can override the k-value of the Fruchteran Reingold layout
139
+
140
+
141
+ ```python
142
+ hv.Graph.from_networkx(G, nx.layout.fruchterman_reingold_layout, k=1)
143
+ ```
144
+
145
+ Finally if we want to layout a Graph after it has already been constructed, the ``layout_nodes`` operation may be used, which also allows applying the ``weight`` argument to graphs which have not been constructed with networkx:
146
+
147
+
148
+ ```python
149
+ from holoviews.element.graphs import layout_nodes
150
+
151
+ graph = hv.Graph([
152
+ ('a', 'b', 3),
153
+ ('a', 'c', 0.2),
154
+ ('c', 'd', 0.1),
155
+ ('c', 'e', 0.7),
156
+ ('c', 'f', 5),
157
+ ('a', 'd', 0.3)
158
+ ], vdims='weight')
159
+
160
+ layout_nodes(graph, layout=nx.layout.fruchterman_reingold_layout, kwargs={'weight': 'weight'})
161
+ ```
162
+
163
+ ## Adding labels
164
+
165
+ If the ``Graph`` we have constructed has additional metadata we can easily use those as labels, we simply get a handle on the nodes, cast them to hv.Labels and then overlay them:
166
+
167
+
168
+ ```python
169
+ graph = hv.Graph.from_networkx(G, nx.layout.fruchterman_reingold_layout)
170
+ labels = hv.Labels(graph.nodes, ['x', 'y'], 'club')
171
+
172
+ (graph * labels.opts(text_font_size='8pt', text_color='white', bgcolor='gray'))
173
+ ```
174
+
175
+ ## Animating graphs
176
+
177
+ Like all other elements ``Graph`` can be updated in a ``HoloMap`` or ``DynamicMap``. Here we animate how the Fruchterman-Reingold force-directed algorithm lays out the nodes in real time.
178
+
179
+
180
+ ```python
181
+ hv.HoloMap({i: hv.Graph.from_networkx(G, nx.spring_layout, iterations=i, seed=10) for i in range(5, 30, 5)},
182
+ kdims='Iterations')
183
+ ```
184
+
185
+ ## Real world graphs
186
+
187
+ As a final example let's look at a slightly larger graph. We will load a dataset of a Facebook network consisting a number of friendship groups identified by their ``'circle'``. We will load the edge and node data using pandas and then color each node by their friendship group using many of the things we learned above.
188
+
189
+
190
+ ```python
191
+ kwargs = dict(width=800, height=800, xaxis=None, yaxis=None)
192
+ opts.defaults(opts.Nodes(**kwargs), opts.Graph(**kwargs))
193
+
194
+ colors = ['#000000']+hv.Cycle('Category20').values
195
+ edges_df = pd.read_csv('../assets/fb_edges.csv')
196
+ fb_nodes = hv.Nodes(pd.read_csv('../assets/fb_nodes.csv')).sort()
197
+ fb_graph = hv.Graph((edges_df, fb_nodes), label='Facebook Circles')
198
+
199
+ fb_graph.opts(cmap=colors, node_size=10, edge_line_width=1,
200
+ node_line_color='gray', node_color='circle')
201
+ ```
202
+
203
+ ## Bundling graphs
204
+
205
+ The datashader library provides algorithms for bundling the edges of a graph and HoloViews provides convenient wrappers around the libraries. Note that these operations need ``scikit-image`` which you can install using:
206
+
207
+ ```
208
+ conda install scikit-image
209
+ ```
210
+
211
+ or
212
+
213
+ ```
214
+ pip install scikit-image
215
+ ```
216
+
217
+
218
+ ```python
219
+ from holoviews.operation.datashader import datashade, bundle_graph
220
+ bundled = bundle_graph(fb_graph)
221
+ bundled
222
+ ```
223
+
224
+ ## Datashading graphs
225
+
226
+ For graphs with a large number of edges we can datashade the paths and display the nodes separately. This loses some of the interactive features but will let you visualize quite large graphs:
227
+
228
+
229
+ ```python
230
+ (datashade(bundled, normalization='linear', width=800, height=800) * bundled.nodes).opts(
231
+ opts.Nodes(color='circle', size=10, width=1000, cmap=colors, legend_position='right'))
232
+ ```
233
+
234
+ ### Applying selections
235
+
236
+ Alternatively we can select the nodes and edges by an attribute that resides on either. In this case we will select the nodes and edges for a particular circle and then overlay just the selected part of the graph on the datashaded plot. Note that selections on the ``Graph`` itself will select all nodes that connect to one of the selected nodes. In this way a smaller subgraph can be highlighted and the larger graph can be datashaded.
237
+
238
+
239
+ ```python
240
+ datashade(bundle_graph(fb_graph), normalization='linear', width=800, height=800) *\
241
+ bundled.select(circle='circle15').opts(node_fill_color='white')
242
+ ```
243
+
244
+ To select just nodes that are in 'circle15' set the ``selection_mode='nodes'`` overriding the default of 'edges':
245
+
246
+
247
+ ```python
248
+ bundled.select(circle='circle15', selection_mode='nodes')
249
+ ```
hvplot_docs/Notebook_Magics.md ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Notebook Magics
2
+
3
+
4
+ ```python
5
+ import pandas as pd
6
+ import numpy as np
7
+ import holoviews as hv
8
+ from holoviews import opts
9
+ hv.extension('bokeh', 'matplotlib')
10
+ ```
11
+
12
+ The [Applying Customizations](03-Applying_Customizations.ipynb) user guide describes the currently *recommended* way to customize your visualizations in HoloViews. Those mechanisms use standard Python syntax but they are not the only way to apply options as there is a much older approach for working with HoloViews that is specific to notebooks.
13
+
14
+ From the start, HoloViews aimed to enable rapid exploration of data in Jupyter Notebooks. For this reason, when you load the HoloViews extension in a notebook, you also get a set of IPython magics. IPython magics use a syntax that is not standard Python and the HoloViews magics only apply in the notebook environment (and not the IPython terminal for instance).
15
+
16
+ The advantages of the notebook magics are:
17
+
18
+ * They allow tab-completion in the notebook environment (but so do the more recent option builders and `hv.output` mechanisms).
19
+ * They allow very concise expression of options and settings.
20
+
21
+
22
+ Unfortunately, they also have some serious disadvantages:
23
+
24
+ * They are not Python syntax which makes it difficult to use code written with magics in notebooks anywhere else. For instance, it makes it harder to use such code with [bokeh server](https://bokeh.pydata.org/en/latest/docs/reference/server.html) or [panel](http://panel.pyviz.org/).
25
+ * They have their own special syntax which is very concise but also rather mysterious.
26
+
27
+
28
+ These disadvantages means the magics can be bewildering to anyone unfamiliar with the IPython specific syntax and HoloViews itself, and are no longer recommended for these reasons. This user guide documents these magics to allow people to understand older notebooks using HoloViews and to help people update these old notebooks to use the recommended Python API.
29
+
30
+ ## Line and cell magics
31
+
32
+ There are two types of magic supported in Jupyter notebooks called *line magics* and *cell magics* respectively. Both typically appear at the top of code cells prefixed by `%` (line magics) or `%%` (cell magics).
33
+
34
+ * **line magics**: These can appear anywhere in a code cell and effect global changes to the current notebook session. HoloViews has the `%opts` and `%output` line magics.
35
+ * **cell magics**: These have to appear at the top of the cell and are used to modify how that cell is executed. HoloViews has the `%%opts` and `%%output` cell magics.
36
+
37
+
38
+
39
+ ## The %opts and %%opts magics
40
+
41
+ These two magics are now served by `opts.defaults` and the `.opts` method respectively, as described in the [Applying Customizations](03-Applying_Customizations.ipynb) user guide.
42
+
43
+ * *The ``%opts`` line magic*: IPython specific syntax applied globally *[string format]*
44
+ * *The ``%%opts`` cell magic*: IPython specific syntax applies to displayed object *[string format]*
45
+
46
+ These magics have their own syntax that separates between *style*, *plot* and *norm* options described towards the end of the [Applying Customizations](03-Applying_Customizations.ipynb) user guide. The definition of the syntax is as follows:
47
+
48
+ ```
49
+ [[spec] [normalization] [plotting options] [style options]]+
50
+
51
+ spec: A dotted type.group.label specification
52
+ (e.g. Curve,Sinusoid.Squared)
53
+
54
+ normalization: List of normalization options delimited by braces.
55
+ One of | -axiswise | -framewise | +axiswise | +framewise |
56
+ E.g. { +axiswise +framewise }
57
+
58
+ plotting options: List of plotting option keywords delimited by
59
+ square brackets. E.g. [show_title=False]
60
+
61
+ style options: List of style option keywords delimited by
62
+ parentheses. E.g. (lw=10 marker='+')
63
+ ```
64
+
65
+ In other words, you have a list of spec strings (for instance `Curve` or `Curve.Sinusoid`) followed by keywords in either parentheses, square brackets or braces to represent the style, plot and normalization options respectively.
66
+
67
+ ## Example `%%opts` cell magic
68
+
69
+ Here is the example from the [Customization](../getting_started/2-Customization.ipynb) section of the 'Getting Started' customized using the `%%opts` cell magic:
70
+
71
+
72
+ ```python
73
+ %%opts Curve [height=200 width=900 xaxis=None tools=['hover']]
74
+ %%opts Curve (color='red' line_width=1.5)
75
+ %%opts Spikes [height=150 width=900 yaxis=None] (color='grey' line_width=0.25)
76
+
77
+ spike_train = pd.read_csv('../assets/spike_train.csv.gz')
78
+ curve = hv.Curve( spike_train, 'milliseconds', vdims='Hertz')
79
+ spikes = hv.Spikes(spike_train, 'milliseconds', vdims=[])
80
+ layout = (curve+spikes).cols(1)
81
+ layout
82
+ ```
83
+
84
+ This `layout` object is now customized in a way that will persist, just like it would using the recommended `.opts` method together with option builders. It is worth noting that instead of just using element names, you can specify the group and label (e.g `Curve.Sinusoid.Squared`) to condition on that metadata, just the way you can using the option builders.
85
+
86
+
87
+ ## Example `%opts` line magic
88
+
89
+ Here is how you could use the `%opts` line magic instead of `opts.default` as detailed in the [Applying Customizations](03-Applying_Customizations.ipynb) user guide:
90
+
91
+
92
+
93
+
94
+ ```python
95
+ %opts HeatMap (cmap='Summer') [colorbar=True, toolbar='above']
96
+ ```
97
+
98
+ Now all `HeatMap` elements will use the 'Summer' colormap, showing a colorbar with the Bokeh toolbar at the top:
99
+
100
+
101
+ ```python
102
+ data = [(chr(65+i), chr(97+j), i*j) for i in range(5) for j in range(5) if i!=j]
103
+ hv.HeatMap(data).sort()
104
+ ```
105
+
106
+ ## The `%output` line magic
107
+
108
+ The `%output` line magic has been fairly directly replaced by the `hv.output` utility. Here is an example of the `%output` line magic:
109
+
110
+
111
+
112
+
113
+ ```python
114
+ %output backend='matplotlib', fig='svg'
115
+ ```
116
+
117
+ This ensures the following `Path` (and all subsequent `Path` objects) are rendered as SVG with matplotlib:
118
+
119
+
120
+ ```python
121
+ lin = np.linspace(0, np.pi*2, 200)
122
+
123
+ def lissajous(t, a, b, delta):
124
+ return (np.sin(a * t + delta), np.sin(b * t), t)
125
+
126
+ path = hv.Path([lissajous(lin, 3, 5, np.pi/2)])
127
+ path.opts(opts.Path(linewidth=2, color='red', linestyle='dotted'))
128
+ ```
129
+
130
+ For the purposes of this notebook, let us switch the plotting extension back to bokeh:
131
+
132
+
133
+ ```python
134
+ %output backend='bokeh'
135
+ ```
136
+
137
+ Note that the `%output` magic accepts the same set of output settings as the `hv.output` utility.
138
+
139
+ ## The `%%output` cell magic
140
+
141
+ If we want to *temporarily* switch to matplotlib with some custom output settings, we can use the `%%output` cell magic in an example combines the `%%output` and `%%opts` cell magics in the same cell:
142
+
143
+
144
+
145
+ ```python
146
+ %%output backend='matplotlib' fig='svg' size=50
147
+ %%opts Path (linewidth=3 color='blue')
148
+ lin = np.linspace(0, np.pi*2, 200)
149
+
150
+ def lissajous(t, a, b, delta):
151
+ return (np.sin(a * t + delta), np.sin(b * t), t)
152
+
153
+ hv.Path([lissajous(lin, 3, 5, np.pi/2)])
154
+ ```
155
+
156
+ The recommended approach would now be to pass the `path` object to the `hv.output` utility as detailed in the [Applying Customizations](03-Applying_Customizations.ipynb) user guide. The magic processes the same set of output settings as the `hv.output` utility.
hvplot_docs/Pandas_API.md ADDED
@@ -0,0 +1,628 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ If hvplot and pandas are both installed, then we can use the pandas.options.plotting.backend to control the output of `pd.DataFrame.plot` and `pd.Series.plot`. This notebook is meant to recreate the [pandas visualization docs](https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html).
2
+
3
+
4
+ ```python
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ pd.options.plotting.backend = 'holoviews'
9
+ ```
10
+
11
+ ## Basic Plotting: `plot`
12
+
13
+ The plot method on Series and DataFrame is just a simple wrapper around `hvplot()`:
14
+
15
+
16
+ ```python
17
+ ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000))
18
+ ts = ts.cumsum()
19
+ ts.plot()
20
+ ```
21
+
22
+ If the index consists of dates, they will be sorted (as long as `sort_date=True`) and formatted the x-axis nicely as per above.
23
+
24
+ On DataFrame, `plot()` is a convenience to plot all of the columns with labels:
25
+
26
+
27
+ ```python
28
+ df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index, columns=list('ABCD'))
29
+ df = df.cumsum()
30
+ df.plot()
31
+ ```
32
+
33
+ You can plot one column versus another using the *x* and *y* keywords in `plot()`:
34
+
35
+
36
+ ```python
37
+ df3 = pd.DataFrame(np.random.randn(1000, 2), columns=['B', 'C']).cumsum()
38
+ df3['A'] = pd.Series(list(range(len(df))))
39
+ df3.plot(x='A', y='B')
40
+ ```
41
+
42
+ **Note** For more formatting and styling options, see [formatting](#plot-formatting) below.
43
+
44
+ ## Other Plots
45
+
46
+ Plotting methods allow for a handful of plot styles other than the default `line` plot. These methods can be provided as the `kind` keyword argument to `plot()`. These include:
47
+
48
+ * `bar` or `barh` for bar plots
49
+ * `hist` for histogram
50
+ * `box` for boxplot
51
+ * `kde` or `density` for density plots
52
+ * `area` for area plots
53
+ * `scatter` for scatter plots
54
+ * `hexbin` for hexagonal bin plots
55
+ * ~~`pie` for pie plots~~
56
+
57
+ For example, a bar plot can be created the following way:
58
+
59
+
60
+ ```python
61
+ df.iloc[5].plot(kind='bar')
62
+ ```
63
+
64
+ You can also create these other plots using the methods DataFrame.plot.<kind> instead of providing the kind keyword argument. This makes it easier to discover plot methods and the specific arguments they use:
65
+
66
+ In addition to these `kind`s, there are the `DataFrame.hist()`, and `DataFrame.boxplot()` methods, which use a separate interface.
67
+
68
+ Finally, there are several plotting functions in `hvplot.plotting` that take a `Series` or `DataFrame` as an argument. These include:
69
+
70
+ * `Scatter Matrix`
71
+ * `Andrews Curves`
72
+ * `Parallel Coordinates`
73
+ * `Lag Plot`
74
+ * ~~`Autocorrelation Plot`~~
75
+ * ~~`Bootstrap Plot`~~
76
+ * ~~`RadViz`~~
77
+
78
+ Plots may also be adorned with errorbars or tables.
79
+
80
+ ### Bar plots
81
+
82
+ For labeled, non-time series data, you may wish to produce a bar plot:
83
+
84
+
85
+
86
+
87
+ ```python
88
+ import holoviews as hv
89
+ ```
90
+
91
+
92
+ ```python
93
+ df.iloc[5].plot.bar() * hv.HLine(0).opts(color='k')
94
+ ```
95
+
96
+ Calling a DataFrame’s `plot.bar()` method produces a multiple bar plot:
97
+
98
+
99
+ ```python
100
+ df2 = pd.DataFrame(np.random.rand(10, 4), columns=['a', 'b', 'c', 'd'])
101
+ df2.plot.bar()
102
+ ```
103
+
104
+ To produce a stacked bar plot, pass `stacked=True`:
105
+
106
+
107
+ ```python
108
+ df2.plot.bar(stacked=True)
109
+ ```
110
+
111
+ To get horizontal bar plots, use the `barh` method:
112
+
113
+
114
+ ```python
115
+ df2.plot.barh(stacked=True)
116
+ ```
117
+
118
+ ### Histograms
119
+
120
+ Histogram can be drawn by using the `DataFrame.plot.hist()` and `Series.plot.hist()` methods.
121
+
122
+
123
+ ```python
124
+ df4 = pd.DataFrame({'a': np.random.randn(1000) + 1, 'b': np.random.randn(1000),
125
+ 'c': np.random.randn(1000) - 1}, columns=['a', 'b', 'c'])
126
+
127
+ df4.plot.hist(alpha=0.5)
128
+ ```
129
+
130
+ Using the matplotlib backend, histograms can be stacked using `stacked=True`; **stacking is not yet supported in the holoviews backend**. Bin size can be changed by `bins` keyword.
131
+
132
+
133
+ ```python
134
+ # Stacked not supported
135
+ df4.plot.hist(stacked=True, bins=20)
136
+ ```
137
+
138
+ You can pass other keywords supported by matplotlib hist. For example, horizontal and cumulative histogram can be drawn by `invert=True` and `cumulative=True`.
139
+
140
+
141
+ ```python
142
+ df4['a'].plot.hist(invert=True, cumulative=True)
143
+ ```
144
+
145
+ The existing interface `DataFrame.hist` to plot histogram still can be used.
146
+
147
+
148
+ ```python
149
+ df['A'].diff().hist()
150
+ ```
151
+
152
+ `DataFrame.hist()` plots the histograms of the columns on multiple subplots:
153
+
154
+
155
+ ```python
156
+ df.diff().hist(color='k', alpha=0.5, bins=50, subplots=True, width=300).cols(2)
157
+ ```
158
+
159
+ The by keyword can be specified to plot grouped histograms:
160
+
161
+
162
+ ```python
163
+ # by does not support arrays, instead the array should be added as a column
164
+ data = pd.Series(np.random.randn(1000))
165
+ data = pd.DataFrame({'data': data, 'by_column': np.random.randint(0, 4, 1000)})
166
+ data.hist(by='by_column', width=300, subplots=True).cols(2)
167
+ ```
168
+
169
+ ### Box Plots
170
+
171
+ Boxplot can be drawn calling `Series.plot.box()` and `DataFrame.plot.box()`, or `DataFrame.boxplot()` to visualize the distribution of values within each column.
172
+
173
+ For instance, here is a boxplot representing five trials of 10 observations of a uniform random variable on [0,1).
174
+
175
+
176
+
177
+ ```python
178
+ df = pd.DataFrame(np.random.rand(10, 5), columns=['A', 'B', 'C', 'D', 'E'])
179
+ df.plot.box()
180
+ ```
181
+
182
+ Using the matplotlib backend, boxplot can be colorized by passing `color` keyword. You can pass a `dict` whose keys are `boxes`, `whiskers`, `medians` and `caps`.
183
+
184
+ **This behavior is not supported in the holoviews backend.**
185
+
186
+ You can pass other keywords supported by holoviews boxplot. For example, horizontal and custom-positioned boxplot can be drawn by `invert=True` and positions keywords.
187
+
188
+
189
+ ```python
190
+ # positions not supported
191
+ df.plot.box(invert=True, positions=[1, 4, 5, 6, 8])
192
+ ```
193
+
194
+ See the boxplot method and the matplotlib boxplot documentation for more.
195
+
196
+ The existing interface `DataFrame.boxplot` to plot boxplot still can be used.
197
+
198
+
199
+ ```python
200
+ df = pd.DataFrame(np.random.rand(10, 5))
201
+ df.boxplot()
202
+ ```
203
+
204
+ You can create a stratified boxplot using the `groupby` keyword argument to create groupings. For instance,
205
+
206
+
207
+ ```python
208
+ df = pd.DataFrame(np.random.rand(10,2), columns=['Col1', 'Col2'])
209
+
210
+ df['X'] = pd.Series(['A','A','A','A','A','B','B','B','B','B'])
211
+
212
+ df.boxplot(col='X')
213
+ ```
214
+
215
+ You can also pass a subset of columns to plot, as well as group by multiple columns:
216
+
217
+
218
+ ```python
219
+ df = pd.DataFrame(np.random.rand(10,3), columns=['Col1', 'Col2', 'Col3'])
220
+
221
+ df['X'] = pd.Series(['A','A','A','A','A','B','B','B','B','B'])
222
+ df['Y'] = pd.Series(['A','B','A','B','A','B','A','B','A','B'])
223
+
224
+ df.boxplot(y=['Col1','Col2'], col='X', row='Y')
225
+ ```
226
+
227
+
228
+ ```python
229
+ df_box = pd.DataFrame(np.random.randn(50, 2))
230
+ df_box['g'] = np.random.choice(['A', 'B'], size=50)
231
+ df_box.loc[df_box['g'] == 'B', 1] += 3
232
+ df_box.boxplot(row='g')
233
+ ```
234
+
235
+ For more control over the ordering of the levels, we can perform a groupby on the data before plotting.
236
+
237
+
238
+ ```python
239
+ df_box.groupby('g').boxplot()
240
+ ```
241
+
242
+ ### Area Plot
243
+
244
+ You can create area plots with `Series.plot.area()` and `DataFrame.plot.area()`. Area plots are *not* stacked by default. To produce stacked area plot, each column must be either all positive or all negative values.
245
+
246
+ When input data contains *NaN*, it will be automatically filled by 0. If you want to drop or fill by different values, use `dataframe.dropna()` or `dataframe.fillna()` before calling plot.
247
+
248
+
249
+ ```python
250
+ df = pd.DataFrame(np.random.rand(10, 4), columns=['a', 'b', 'c', 'd'])
251
+ df.plot.area(alpha=0.5)
252
+ ```
253
+
254
+ To produce an stacked plot, pass `stacked=True`.
255
+
256
+
257
+ ```python
258
+ df.plot.area(stacked=True)
259
+ ```
260
+
261
+ ### Scatter Plot
262
+
263
+ Scatter plot can be drawn by using the `DataFrame.plot.scatter()` method. Scatter plot require that x and y be specified using the `x` and `y` keywords.
264
+
265
+
266
+ ```python
267
+ df = pd.DataFrame(np.random.rand(50, 4), columns=['a', 'b', 'c', 'd'])
268
+ df.plot.scatter(x='a', y='b')
269
+ ```
270
+
271
+ To plot multiple column groups in a single axes, repeat `plot` method and then use the overlay operator (`*`) to combine the plots. It is recommended to specify color and label keywords to distinguish each groups.
272
+
273
+
274
+ ```python
275
+ plot_1 = df.plot.scatter(x='a', y='b', color='DarkBlue', label='Group 1')
276
+ plot_2 = df.plot.scatter(x='c', y='d', color='DarkGreen', label='Group 2')
277
+
278
+ plot_1 * plot_2
279
+ ```
280
+
281
+ The keyword `c` may be given as the name of a column to provide colors for each point:
282
+
283
+
284
+ ```python
285
+ df.plot.scatter(x='a', y='b', c='c', s=50)
286
+ ```
287
+
288
+ You can pass other keywords supported by matplotlib `scatter`. The example below shows a bubble chart using a column of the `DataFrame` as the bubble size.
289
+
290
+
291
+ ```python
292
+ df.plot.scatter(x='a', y='b', s=df['c']*500)
293
+ ```
294
+
295
+ The same effect can be accomplished using the `scale` option.
296
+
297
+
298
+ ```python
299
+ df.plot.scatter(x='a', y='b', s='c', scale=25)
300
+ ```
301
+
302
+ ### Hexagonal Bin Plot
303
+
304
+
305
+ You can create hexagonal bin plots with `DataFrame.plot.hexbin()`. Hexbin plots can be a useful alternative to scatter plots if your data are too dense to plot each point individually.
306
+
307
+
308
+ ```python
309
+ df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])
310
+ df['b'] = df['b'] + np.arange(1000)
311
+
312
+ df.plot.hexbin(x='a', y='b', gridsize=25, width=500, height=400)
313
+ ```
314
+
315
+ A useful keyword argument is `gridsize`; it controls the number of hexagons in the x-direction, and defaults to 100. A larger `gridsize` means more, smaller bins.
316
+
317
+ By default, a histogram of the counts around each `(x, y)` point is computed. You can specify alternative aggregations by passing values to the `C` and `reduce_C_function` arguments. `C` specifies the value at each `(x, y)` point and `reduce_C_function` is a function of one argument that reduces all the values in a bin to a single number (e.g. `mean`, `max`, `sum`, `std`). In this example the positions are given by columns `a` and `b`, while the value is given by column z. The bins are aggregated with numpy’s `max` function.
318
+
319
+
320
+ ```python
321
+ df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])
322
+ df['b'] = df['b'] = df['b'] + np.arange(1000)
323
+ df['z'] = np.random.uniform(0, 3, 1000)
324
+
325
+ df.plot.hexbin(x='a', y='b', C='z', reduce_function=np.max, gridsize=25, width=500, height=400)
326
+ ```
327
+
328
+ ### Density Plot
329
+
330
+ You can create density plots using the `Series.plot.kde()` and `DataFrame.plot.kde()` methods.
331
+
332
+
333
+ ```python
334
+ ser = pd.Series(np.random.randn(1000))
335
+
336
+ ser.plot.kde()
337
+ ```
338
+
339
+ ### Pie plot
340
+
341
+ **NOT SUPPORTED**
342
+
343
+
344
+ ## Plotting Tools
345
+
346
+ These functions can be imported from `hvplot.plotting` and take a `Series` or `DataFrame` as an argument.
347
+
348
+ ### Scatter Matrix Plot
349
+
350
+ You can create a scatter plot matrix using the `scatter_matrix` function:
351
+
352
+
353
+ ```python
354
+ import pandas as pd, numpy as np
355
+ ```
356
+
357
+
358
+ ```python
359
+ from hvplot import scatter_matrix
360
+
361
+ df = pd.DataFrame(np.random.randn(1000, 4), columns=['a', 'b', 'c', 'd'])
362
+
363
+ scatter_matrix(df, alpha=0.2, diagonal='kde')
364
+ ```
365
+
366
+ Since scatter matrix plots may generate a large number of points (the one above renders 120,000 points!), you may want to take advantage of the power provided by [Datashader](https://datashader.org/) to rasterize the off-diagonal plots into a fixed-resolution representation:
367
+
368
+
369
+ ```python
370
+ df = pd.DataFrame(np.random.randn(10000, 4), columns=['a', 'b', 'c', 'd'])
371
+
372
+ scatter_matrix(df, rasterize=True, dynspread=True)
373
+ ```
374
+
375
+ ### Andrews Curves
376
+
377
+ Andrews curves allow one to plot multivariate data as a large number of curves that are created using the attributes of samples as coefficients for Fourier series. By coloring these curves differently for each class it is possible to visualize data clustering. Curves belonging to samples of the same class will usually be closer together and form larger structures.
378
+
379
+
380
+
381
+
382
+ ```python
383
+ from bokeh.sampledata import iris
384
+ from hvplot import andrews_curves
385
+
386
+ data = iris.flowers
387
+
388
+ andrews_curves(data, 'species')
389
+ ```
390
+
391
+ ### Parallel Coordinates
392
+
393
+ Parallel coordinates is a plotting technique for plotting multivariate data. It allows one to see clusters in data and to estimate other statistics visually. Using parallel coordinates points are represented as connected line segments. Each vertical line represents one attribute. One set of connected line segments represents one data point. Points that tend to cluster will appear closer together.
394
+
395
+
396
+ ```python
397
+ from hvplot import parallel_coordinates
398
+
399
+ parallel_coordinates(data, 'species')
400
+ ```
401
+
402
+ ### Lag Plot
403
+
404
+ Lag plots are used to check if a data set or time series is random. Random data should not exhibit any structure in the lag plot. Non-random structure implies that the underlying data are not random.
405
+
406
+
407
+ ```python
408
+ from hvplot import lag_plot
409
+
410
+ data = pd.Series(0.1 * np.random.rand(1000) +
411
+ 0.9 * np.sin(np.linspace(-99 * np.pi, 99 * np.pi, num=1000)))
412
+
413
+ lag_plot(data, width=400, height=400)
414
+ ```
415
+
416
+
417
+ ### Autocorrelation Plot
418
+
419
+ **NOT SUPPORTED**
420
+
421
+ ### Bootstrap Plot
422
+
423
+ **NOT SUPPORTED**
424
+
425
+ ### RadViz
426
+
427
+ **NOT SUPPORTED**
428
+
429
+
430
+
431
+
432
+ ## Plot Formatting
433
+
434
+ ### General plot style arguments
435
+
436
+ Most plotting methods have a set of keyword arguments that control the layout and formatting of the returned plot:
437
+
438
+
439
+ ```python
440
+ ts.plot(c='k', line_dash='dashed', label='Series')
441
+ ```
442
+
443
+ For each kind of plot (e.g. *line*, *bar*, *scatter*) any additional arguments keywords are passed along to the corresponding holoviews object (`hv.Curve`, `hv.Bar`, `hv.Scatter`). These can be used to control additional styling, beyond what pandas provides.
444
+
445
+ ### Controlling the Legend
446
+
447
+ You may set the `legend` argument to `False` to hide the legend, which is shown by default. You can also control the placement of the legend using the same `legend` argument set to one of: 'top_right', 'top_left', 'bottom_left', 'bottom_right', 'right', 'left', 'top', or 'bottom'.
448
+
449
+
450
+ ```python
451
+ df = pd.DataFrame(np.random.randn(1000, 4),
452
+ index=ts.index, columns=list('ABCD'))
453
+ df = df.cumsum()
454
+
455
+ df.plot(legend=False)
456
+ ```
457
+
458
+
459
+ ```python
460
+ df.plot(legend='top_left')
461
+ ```
462
+
463
+ ### Scales
464
+
465
+ You may pass `logy` to get a log-scale Y axis, similarly use `logx` to get a log-scale on the X axis or `logz` to get a log-scale on the color axis.
466
+
467
+
468
+ ```python
469
+ ts = pd.Series(np.random.randn(1000),
470
+ index=pd.date_range('1/1/2000', periods=1000))
471
+
472
+ ts = np.exp(ts.cumsum())
473
+
474
+ ts.plot(logy=True)
475
+ ```
476
+
477
+
478
+ ```python
479
+ df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])
480
+ df['b'] = df['b'] + np.arange(1000)
481
+ df.plot.hexbin(x='a', y='b', gridsize=25, width=500, height=400, logz=True)
482
+ ```
483
+
484
+ ### Plotting on a Secondary Y-axis
485
+
486
+ **NOT SUPPORTED**
487
+
488
+ ### Suppressing tick resolution adjustment
489
+ pandas includes automatic tick resolution adjustment for regular frequency time-series data. For limited cases where pandas cannot infer the frequency information (e.g., in an externally created twinx), you can choose to suppress this behavior for alignment purposes.
490
+
491
+ Here is the default behavior, notice how the x-axis tick labeling is performed:
492
+
493
+
494
+ ```python
495
+ df = pd.DataFrame(np.random.randn(1000, 4),
496
+ index=ts.index, columns=list('ABCD'))
497
+ df = df.cumsum()
498
+ df.A.plot()
499
+ ```
500
+
501
+ Formatters can be set using format strings, by declaring bokeh TickFormatters, or using custom functions. See [HoloViews Tick Docs](https://holoviews.org/user_guide/Customizing_Plots.html#Axis-ticks) for more information.
502
+
503
+
504
+ ```python
505
+ from bokeh.models.formatters import DatetimeTickFormatter
506
+
507
+ formatter = DatetimeTickFormatter(months='%b %Y')
508
+ ```
509
+
510
+
511
+ ```python
512
+ df.A.plot(yformatter='$%.2f', xformatter=formatter)
513
+ ```
514
+
515
+ ### Subplots
516
+
517
+ Each `Series` in a `DataFrame` can be plotted on a different axis with the `subplots` keyword:
518
+
519
+
520
+ ```python
521
+ df = pd.DataFrame(np.random.randn(1000, 4),
522
+ index=ts.index, columns=list('ABCD'))
523
+ df = df.cumsum()
524
+ df.plot(subplots=True, height=150).cols(1)
525
+ ```
526
+
527
+ ### Controlling layout and targeting multiple axes
528
+
529
+ The layout of subplots can be specified using `.cols(n)`.
530
+
531
+
532
+ ```python
533
+ df.plot(subplots=True, shared_axes=False, width=150).cols(3)
534
+ ```
535
+
536
+ ### Plotting with error bars
537
+
538
+ Plotting with error bars is *not* supported in `DataFrame.plot()` and `Series.plot()`. To add errorbars, users should fall back to using hvplot directly.
539
+
540
+
541
+ ```python
542
+ import hvplot.pandas # noqa
543
+ ```
544
+
545
+
546
+ ```python
547
+ df3 = pd.DataFrame({'data1': [3, 2, 4, 3, 2, 4, 3, 2],
548
+ 'data2': [6, 5, 7, 5, 4, 5, 6, 5]})
549
+ mean_std = pd.DataFrame({'mean': df3.mean(), 'std': df3.std()})
550
+ mean_std
551
+ ```
552
+
553
+
554
+ ```python
555
+ (mean_std.plot.bar(y='mean', alpha=0.7) * \
556
+ mean_std.hvplot.errorbars(x='index', y='mean', yerr1='std')
557
+ ).opts(show_legend=False).redim.range(mean=(0, 6.5))
558
+ ```
559
+
560
+ ### Plotting tables
561
+
562
+ The matplotlib backend includes support for the `table` keyword in `DataFrame.plot()` and `Series.plot()`. This same effect can be accomplished with holoviews by using `hvplot.table` and creating a layout of the resulting plots.
563
+
564
+
565
+ ```python
566
+ df = pd.DataFrame(np.random.rand(5, 3), columns=['a', 'b', 'c'])
567
+
568
+ (df.plot(xaxis=False, legend='top_right') + \
569
+ df.T.hvplot.table()).cols(1)
570
+ ```
571
+
572
+ ### Colormaps
573
+
574
+ A potential issue when plotting a large number of columns is that it can be difficult to distinguish some series due to repetition in the default colors. To remedy this, DataFrame plotting supports the use of the `colormap` argument, which accepts either a colormap or a string that is a name of a colormap.
575
+
576
+ To use the cubehelix colormap, we can pass `colormap='cubehelix'`.
577
+
578
+
579
+ ```python
580
+ df = pd.DataFrame(np.random.randn(1000, 10), index=ts.index)
581
+ df = df.cumsum()
582
+ df.plot(colormap='cubehelix')
583
+ ```
584
+
585
+ Colormaps can also be used with other plot types, like bar charts:
586
+
587
+
588
+ ```python
589
+ dd = pd.DataFrame(np.random.randn(10, 10)).applymap(abs)
590
+ dd = dd.cumsum()
591
+ dd.plot.bar(colormap='Greens')
592
+ ```
593
+
594
+ Parallel coordinates charts:
595
+
596
+
597
+ ```python
598
+ from bokeh.sampledata import iris
599
+ from hvplot import parallel_coordinates, andrews_curves
600
+
601
+ data = iris.flowers
602
+ parallel_coordinates(data, 'species', colormap='gist_rainbow')
603
+ ```
604
+
605
+ Andrews curves charts:
606
+
607
+
608
+ ```python
609
+ andrews_curves(data, 'species', colormap='winter')
610
+ ```
611
+
612
+ ## Plotting directly with holoviews
613
+
614
+ In some situations it may still be preferable or necessary to prepare plots directly with hvplot or holoviews, for instance when a certain type of plot or customization is not (yet) supported by pandas. `Series` and `DataFrame` objects behave like arrays and can therefore be passed directly to holoviews functions without explicit casts.
615
+
616
+
617
+ ```python
618
+ import holoviews as hv
619
+
620
+ price = pd.Series(np.random.randn(150).cumsum(),
621
+ index=pd.date_range('2000-1-1', periods=150, freq='B'), name='price')
622
+ ma = price.rolling(20).mean()
623
+ mstd = price.rolling(20).std()
624
+
625
+ price.plot(c='k') * ma.plot(c='b', label='mean') * \
626
+ hv.Area((mstd.index, ma - 2 * mstd, ma + 2 * mstd),
627
+ vdims=['y', 'y2']).opts(color='b', alpha=0.2)
628
+ ```
hvplot_docs/Plots_and_Renderers.md ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import holoviews as hv
3
+ import numpy as np
4
+ ```
5
+
6
+ HoloViews ordinarily hides the plotting machinery from the user. This allows for very quick iteration over different visualizations to explore a dataset, however it is often important to customize the precise details of a plot. HoloViews makes it very easy to customize existing plots, or even create completely novel plots. This manual will provide a general overview of the plotting system.
7
+
8
+ The separation of the data from the precise details of the visualization is one of the core principles of the HoloViews. [``Elements``](http://holoviews.org/reference/index.html#elements) provide thin wrappers around chunks of actual data, while [containers](http://holoviews.org/reference/index.html#containers) allow composing these Elements into overlays, layouts, grids and animations/widgets. Each Element or container type has a corresponding plotting class, which renders a visual representation of the data for a particular backend. While the precise details of the implementation differ between backends to accommodate the vastly different APIs plotting backends provide, many of the high-level details are shared across backends.
9
+
10
+ # The Store object
11
+
12
+ The association between an Element or container and the backend specific plotting class is held on the global ``Store`` object. The ``Store`` object holds a ``registry`` of plot objects for each backend. We can view the registry for each backend by accessing ``Store.registry`` directly:
13
+
14
+
15
+ ```python
16
+ import holoviews.plotting.mpl
17
+ list(hv.Store.registry['matplotlib'].items())[0:5]
18
+ ```
19
+
20
+ The Store object provides a global registry not only for the plots themselves but also creates an entry in the OptionsTree for that particular backend. This allows options for that backend to be validated and enables setting plot, style and normalization options via the [Options system](03-Applying_Customizations.ipynb) system. We can view the ``OptionsTree`` object by requesting it from the store. We'll make a copy with just the first few entries so we can view the structure of the tree:
21
+
22
+
23
+ ```python
24
+ opts = hv.Store.options(backend='matplotlib')
25
+ hv.core.options.OptionTree(opts.items()[0:10], groups=['plot', 'style', 'norm'])
26
+ ```
27
+
28
+ # Saving and rendering
29
+
30
+ The easiest entry points to rendering a HoloViews object either to the backend specific representation (e.g. a matplotlib figure) or directly to file are the ``hv.render`` and ``hv.save`` functions. Both are shortcuts for using an actual ``Renderer`` object, which will be introduced in the next section. To start with we will create a simple object, a ``Scatter`` element:
31
+
32
+
33
+ ```python
34
+ curve = hv.Curve(range(10))
35
+ curve
36
+ ```
37
+
38
+ This abstract and declarative representation of a ``Curve`` can be turned into an actual plot object, defaulting to the currently selected (or default) backend:
39
+
40
+
41
+ ```python
42
+ fig = hv.render(curve)
43
+ print(type(fig))
44
+ ```
45
+
46
+ By providing an explicit ``backend`` keyword the plot can be rendered using a different backend.
47
+
48
+
49
+ ```python
50
+ p = hv.render(curve, backend='bokeh')
51
+ print(type(p))
52
+ ```
53
+
54
+ This can often be useful to customize a plot in more detail by tweaking styling in ways that are not directly supported by HoloViews. An alternative to ``hv.render`` in case you do not want to fully switch to the underlying plotting API are ``hooks``, which are a plot option on all elements. These allow defining hooks which modify a plot after it has been rendered in the backend but before it is displayed.
55
+
56
+ A ``hook`` is given the HoloViews plot instance and the currently rendered element and thereby provides access to the rendered plot object to apply any customizations and changes which are not exposed by HoloViews:
57
+
58
+
59
+ ```python
60
+ def hook(plot, element):
61
+ # Allows accessing the backends figure object
62
+ plot.state
63
+
64
+ # The handles contain common plot objects
65
+ plot.handles
66
+
67
+ curve = curve.opts(hooks=[hook])
68
+ ```
69
+
70
+ Hooks allow for extensive tweaking of objects before they are finished rendering, without having to entirely abandon HoloViews' API and render the backend's plot object manually.
71
+
72
+ In much the same way the ``hv.save`` function allows exporting plots straight to a file, by default inferring the format from the file extension:
73
+
74
+
75
+ ```python
76
+ hv.save(curve, 'curve.png')
77
+ ```
78
+
79
+ Just like ``hv.render`` the ``hv.save`` function also allows specifying an explicit backend.
80
+
81
+
82
+ ```python
83
+ hv.save(curve, 'curve.html', backend='bokeh')
84
+ ```
85
+
86
+ Additionally for ambiguous file extensions such as HTML it may be necessary to specify an explicit fmt to override the default, e.g. in the case of 'html' output the widgets will default to ``fmt='widgets'``, which may be changed to scrubber widgets using ``fmt='scrubber'``.
87
+
88
+ # Renderers
89
+
90
+ HoloViews provides a general ``Renderer`` baseclass, which defines a general interface to render the output from different backends to a number of standard output formats such as ``png``, ``html`` or ``svg``. The ``__call__`` method on the Renderer automatically looks up and instantiates the registered plotting classes for an object it is passed and then returns the output in the requested format. To make this a bit clearer we'll break this down step by step. First we'll get a handle on the ``MPLRenderer`` and create an object to render.
91
+
92
+ Renderers aren't registered with the Store until the corresponding backends have been imported. Loading the notebook extension with ``hv.notebook_extension('matplotlib')`` is one way of loading a backend and registering a renderer. Another is to simply import the corresponding plotting module as we did above.
93
+
94
+
95
+ ```python
96
+ hv.Store.renderers
97
+ ```
98
+
99
+ This is one way to access a Renderer, another is to instantiate a Renderer instance directly, allowing you to override some of the default plot options.
100
+
101
+
102
+ ```python
103
+ renderer = hv.plotting.mpl.MPLRenderer.instance(dpi=120)
104
+ ```
105
+
106
+ The recommended way to get a handle on a renderer is to use the ``hv.renderer`` function which will also handle imports for you:
107
+
108
+
109
+ ```python
110
+ hv.renderer('matplotlib')
111
+ ```
112
+
113
+ # Working with a Renderer
114
+
115
+ A ``Renderer`` in HoloViews is responsible for instantiating a HoloViews plotting class. It does this by looking up the plotting class in the ``Store.registry``:
116
+
117
+
118
+ ```python
119
+ hv.Store.registry['matplotlib'][hv.Curve]
120
+ ```
121
+
122
+ If we create a ``Curve`` we can instantiate a plotting class from it using the ``Renderer.get_plot`` method:
123
+
124
+
125
+ ```python
126
+ curve = hv.Curve(range(10))
127
+ curve_plot = renderer.get_plot(curve)
128
+ curve_plot
129
+ ```
130
+
131
+ We will revisit how to work with ``Plot`` instances later. For now all we need to know is that they are responsible for translating the HoloViews object (like the ``Curve``) into a backend specific plotting object, accessible on ``Plot.state``:
132
+
133
+
134
+ ```python
135
+ print(type(curve_plot.state), curve_plot.state)
136
+ curve_plot.state
137
+ ```
138
+
139
+ In case of the matplotlib backend this is a ``Figure`` object. However the ``Renderer`` ignores the specific representation of the plot, instead providing a unified interface to translating it into a representation that can displayed, i.e. either an image format or an HTML representation.
140
+
141
+ In this way we can convert the curve directly to its ``png`` representation by calling the ``Renderer`` with the object and the format:
142
+
143
+
144
+ ```python
145
+ from IPython.display import display_png
146
+ png, info = renderer(curve, fmt='png')
147
+ print(info)
148
+ display_png(png, raw=True)
149
+ ```
150
+
151
+ <div class="alert alert-success">
152
+ Tip: To find more information about any HoloViews object use <code>hv.help</code> to print a detailed docstring.
153
+ </div>
154
+
155
+ The valid figure display formats can be seen in the docstring of the Renderer or directly on the parameter:
156
+
157
+
158
+ ```python
159
+ renderer.param['fig'].objects
160
+ ```
161
+
162
+ Note that to export PNG files using the [bokeh renderer](./Plotting_with_Bokeh.ipynb) you may need to install some additional dependencies [detailed here](https://bokeh.pydata.org/en/latest/docs/user_guide/export.html). If you are using conda, it is currently sufficient to run ``conda install selenium phantomjs pillow``.
163
+
164
+ In this way we can easily render the plot in different formats:
165
+
166
+
167
+ ```python
168
+ from IPython.display import display_svg
169
+ svg, info = renderer(curve, fmt='svg')
170
+ print(info)
171
+ display_svg(svg, raw=True)
172
+ ```
173
+
174
+ We could save these byte string representations ourselves but the ``Renderer`` provides a convenient ``save`` method to do so. Simply supply the object the filename and the format, which doubles as the file extension:
175
+
176
+
177
+ ```python
178
+ renderer.save(curve, '/tmp/test', fmt='png')
179
+ ```
180
+
181
+ Another convenient way to render the object is to wrap it in HTML, which we can do with the ``html`` method:
182
+
183
+
184
+ ```python
185
+ from IPython.display import display_html
186
+ html = renderer.html(curve)
187
+ display_html(html, raw=True)
188
+ ```
189
+
190
+ Rendering plots containing ``HoloMap`` and ``DynamicMap`` objects will automatically generate a Panel HoloViews pane which can be rendered in the notebook, saved or rendered as a server app:
191
+
192
+
193
+ ```python
194
+ holomap = hv.HoloMap({i: hv.Image(np.random.rand(10, 10)) for i in range(3)})
195
+ widget = renderer.get_widget(holomap, 'widgets')
196
+ widget
197
+ ```
198
+
199
+ However most of the time it is more convenient to let the Renderer export the widget HTML, again via a convenient method, which will export a HTML document with all the required JS and CSS dependencies:
200
+
201
+
202
+ ```python
203
+ html = renderer.static_html(holomap)
204
+ ```
205
+
206
+ This covers the basics of working with HoloViews renderers. This API is consistent across plotting backends, whether matplotlib, bokeh or plotly.
207
+
208
+ # Plots
209
+
210
+ Above we saw how the Renderer looks up the appropriate plotting class but so far we haven't seen how the plotting actually works. Since HoloViews already nests the data into semantically meaningful components, which define the rough layout of the plots on the page, the plotting classes follow roughly the same hierarchy. To review this hierarchy have a look at the [nesting diagram](./06-Building_Composite_Objects.ipynb#Nesting-hierarchy-) in the Building Composite objects guide.
211
+
212
+ The Layout and GridSpace plotting classes set up the figure and axes appropriately and then instantiate the subplots for all the objects that are contained within. For this purpose we will create a relatively complex object, a ``Layout`` of ``HoloMap``s containing ``Overlay``s containing ``Elements``. We'll instantiate the matching plotting hierarchy and then inspect it.
213
+
214
+
215
+ ```python
216
+ hmap1 = hv.HoloMap({i: hv.Image(np.random.rand(10, 10)) * hv.Ellipse(0, 0, 0.2*i) for i in range(5)})
217
+ element = hv.Curve((range(10), np.random.rand(10)))
218
+ layout = hmap1 + element
219
+ ```
220
+
221
+ We can see the hierarchy in the object's repr:
222
+
223
+
224
+ ```python
225
+ print( repr(layout) )
226
+ ```
227
+
228
+ Now that we've created the object we can again use the ``MPLRenderer`` to instantiate the plot:
229
+
230
+
231
+ ```python
232
+ layout_plot = renderer.get_plot(layout)
233
+ layout_plot
234
+ ```
235
+
236
+ During instantiation the LayoutPlot expanded each object and created subplots. We can access them via a row, column based index and thereby view the first plot.
237
+
238
+
239
+ ```python
240
+ adjoint_plot = layout_plot.subplots[0, 0]
241
+ adjoint_plot
242
+ ```
243
+
244
+ This plotting layer handles plots adjoined to the plot. They are indexed by their position in the AdjointLayout which may include 'top', 'right' and 'main':
245
+
246
+
247
+ ```python
248
+ overlay_plot = adjoint_plot.subplots['main']
249
+ overlay_plot
250
+ ```
251
+
252
+ Now we've drilled all the way down to the OverlayPlot level, we see as expected that this contains two further subplots, one for the ``Image`` and one for ``Text`` Element.
253
+
254
+
255
+ ```python
256
+ overlay_plot.subplots.keys()
257
+ ```
258
+
259
+ Now you might have noticed that the HoloMap seems to have disappeared from the hierarchy. This is because updating a particular plot is handled by the ``ElementPlots`` itself. With that knowledge we can now have a look at the actual plotting API.
260
+
261
+ ### Traversing plots
262
+
263
+ When working with such deeply nested plots accessing leafs can be a lot of effort, therefore the plots also provide a ``traverse`` method letting you specify the types of plots you want to access:
264
+
265
+
266
+ ```python
267
+ layout_plot.traverse(specs=[hv.plotting.mpl.CurvePlot])
268
+ ```
269
+
270
+ # Plotting API
271
+
272
+ There a few methods shared by all plotting classes, which allow the renderer to easily create, update and render a plot. The three most important methods and attributes are:
273
+
274
+ * ``Plot.__init__`` - The constructor already handles a lot of the processing in a plot, it sets up all the subplots if there are any, computes ranges across the object to normalize the display, sets the options that were specified and instantiates the figure, axes, model graphs, or canvas objects depending on the backend.
275
+ * ``Plot.initialize_plot`` - This method draws the initial frame to the appropriate figure, axis or canvas, setting up the various artists (matplotlib) or glyphs (bokeh).
276
+ * ``Plot.update`` - This method updates an already instantiated plot with the data corresponding to the supplied key. This key should match the key in the HoloMap.
277
+
278
+ ### Initializing
279
+
280
+ The Renderer and the widgets use these three methods to instantiate and update a plot to render both static frames and animations or widgets as defined by the ``HoloMap`` or ``DynamicMap``. Above we already instantiated a plot, now we initialize it, thereby drawing the first (or rather last frame).
281
+
282
+
283
+ ```python
284
+ fig = layout_plot.initialize_plot()
285
+ fig
286
+ ```
287
+
288
+ ### Updating
289
+
290
+ We can see ``initialize_plot`` has rendered the last frame with the key ``4``. We can update the figure with another key simply by calling the ``Plot.update`` method with the corresponding key.
291
+
292
+
293
+ ```python
294
+ layout_plot.update(0)
295
+ ```
296
+
297
+
298
+ ```python
299
+ plot = hv.plotting.mpl.RasterPlot(holomap)
300
+ plot
301
+ ```
302
+
303
+ Internally each level of the plotting hierarchy updates all the objects below it, all the way down to the ElementPlots, which handle updating the plotting data.
304
+
305
+ ### Dynamic plot updates
306
+
307
+ Since DynamicMaps may be updated based on stream events they don't work via quite the same API. Each stream automatically captures the plots it is attached to and whenever it receives an event it will update the plot.
308
+
309
+
310
+ ```python
311
+ dmap = hv.DynamicMap(lambda x: hv.Points(np.arange(x)), kdims=[], streams=[hv.streams.PointerX(x=10)])
312
+ plot = renderer.get_plot(dmap)
313
+ plot.initialize_plot()
314
+ ```
315
+
316
+ Internally the update to the stream value will automatically trigger the plot to update, here we will disable this by setting ``trigger=False`` and explicitly calling ``refresh`` on the plot:
317
+
318
+
319
+ ```python
320
+ dmap.event(x=20,)
321
+ plot.refresh()
322
+ plot.state
323
+ ```
324
+
325
+ ### Plotting handles
326
+
327
+ In addition to accessing the overall state of the plot each plotting class usually keeps direct handles for important plotting elements on the ``handles`` attribute:
328
+
329
+
330
+ ```python
331
+ plot.handles
332
+ ```
333
+
334
+ Here we can see how the ``PointPlot`` keeps track of the artist, which is a matplotlib ``PathCollection``, the axis, the figure and the plot title. In addition all matplotlib plots also keep track of a list of any additional plotting elements which should be considered in the bounding box calculation.
335
+
336
+ This is only the top-level API, which is used by the Renderer to render the plot, animation or widget. Each backend has internal APIs to create and update the various plot components.
337
+
338
+ ### Using the matplotlib renderer outside of a Jupyter notebook
339
+
340
+ The matplotlib renderer can be used outside of a notebook by using the `show` method. If running in a python script, this will cause the usual matplotlib window to appear. This is done as follows:
341
+
342
+ mr = hv.renderer('matplotlib')
343
+ mr.show(curve)
hvplot_docs/Plotting.md ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ As we discovered in the [Introduction](Introduction.ipynb), HoloViews allows plotting a variety of data types. Here we will use the sample data module and load the pandas and dask hvPlot API:
2
+
3
+
4
+ ```python
5
+ import numpy as np
6
+ import hvplot.pandas # noqa
7
+ import hvplot.dask # noqa
8
+ ```
9
+
10
+ As we learned the hvPlot API closely mirrors the [Pandas plotting API](https://pandas.pydata.org/pandas-docs/stable/visualization.html), but instead of generating static images when used in a notebook, it uses HoloViews to generate either static or dynamically streaming Bokeh plots. Static plots can be used in any context, while streaming plots require a live [Jupyter notebook](https://jupyter.org), a deployed [Bokeh Server app](https://bokeh.pydata.org/en/latest/docs/user_guide/server.html), or a deployed [Panel](https://panel.holoviz.org) app.
11
+
12
+ HoloViews provides an extensive, very rich set of objects along with a powerful set of operations to apply, as you can find out in the [HoloViews User Guide](https://holoviews.org/user_guide/index.html). But here we will focus on the most essential mechanisms needed to make your data visualizable, without having to worry about the mechanics going on behind the scenes.
13
+
14
+ We will be focusing on two different datasets:
15
+
16
+ - A small CSV file of US crime data, broken down by state
17
+ - A larger Parquet-format file of airline flight data
18
+
19
+ The ``hvplot.sample_data`` module makes these datasets Intake data catalogue, which we can load either using pandas:
20
+
21
+
22
+ ```python
23
+ from hvplot.sample_data import us_crime, airline_flights
24
+
25
+ crime = us_crime.read()
26
+ print(type(crime))
27
+ crime.head()
28
+ ```
29
+
30
+ Or using dask as a ``dask.DataFrame``:
31
+
32
+
33
+ ```python
34
+ flights = airline_flights.to_dask().persist()
35
+ print(type(flights))
36
+ flights.head()
37
+ ```
38
+
39
+ ## The plot interface
40
+
41
+ The ``dask.dataframe.DataFrame.hvplot``, ``pandas.DataFrame.hvplot`` and ``intake.DataSource.plot`` interfaces (and Series equivalents) from HvPlot provide a powerful high-level API to generate complex plots. The ``.hvplot`` API can be called directly or used as a namespace to generate specific plot types.
42
+
43
+ ### The plot method
44
+
45
+ The most explicit way to use the plotting API is to specify the names of columns to plot on the ``x``- and ``y``-axis respectively:
46
+
47
+
48
+ ```python
49
+ crime.hvplot.line(x='Year', y='Violent Crime rate')
50
+ ```
51
+
52
+ As you'll see in more detail below, you can choose which kind of plot you want to use for the data:
53
+
54
+
55
+ ```python
56
+ crime.hvplot(x='Year', y='Violent Crime rate', kind='scatter')
57
+ ```
58
+
59
+ To group the data by one or more additional columns, specify an additional ``by`` variable. As an example here we will plot the departure delay ('depdelay') as a function of 'distance', grouping the data by the 'carrier'. There are many available carriers, so we will select only two of them so that the plot is readable:
60
+
61
+
62
+ ```python
63
+ flight_subset = flights[flights.carrier.isin(['OH', 'F9'])]
64
+ flight_subset.hvplot(x='distance', y='depdelay', by='carrier', kind='scatter', alpha=0.2, persist=True)
65
+ ```
66
+
67
+ Here we have specified the `x` axis explicitly, which can be omitted if the Pandas index column is already the desired x axis. Similarly, here we specified the `y` axis; by default all of the non-index columns would be plotted (which would be a lot of data in this case). If you don't specify the 'y' axis, it will have a default label named 'value', but you can then provide a y axis label explicitly using the ``value_label`` option.
68
+
69
+ Putting all of this together we will plot violent crime, robbery, and burglary rates on the y-axis, specifying 'Year' as the x, and relabel the y-axis to display the 'Rate'.
70
+
71
+
72
+ ```python
73
+ crime.hvplot(x='Year', y=['Violent Crime rate', 'Robbery rate', 'Burglary rate'],
74
+ value_label='Rate (per 100k people)')
75
+ ```
76
+
77
+ ### The hvplot namespace
78
+
79
+ Instead of using the ``kind`` argument to the plot call, we can use the ``hvplot`` namespace, which lets us easily discover the range of plot types that are supported. Use tab completion to explore the available plot types:
80
+
81
+ ```python
82
+ crime.hvplot.<TAB>
83
+ ```
84
+
85
+ Plot types available include:
86
+
87
+ * <a href="#area">``.area()``</a>: Plots a area chart similar to a line chart except for filling the area under the curve and optionally stacking
88
+ * <a href="#bars">``.bar()``</a>: Plots a bar chart that can be stacked or grouped
89
+ * <a href="#bivariate">``.bivariate()``</a>: Plots 2D density of a set of points
90
+ * <a href="#box-whisker-plots">``.box()``</a>: Plots a box-whisker chart comparing the distribution of one or more variables
91
+ * <a href="#heatmap">``.heatmap()``</a>: Plots a heatmap to visualizing a variable across two independent dimensions
92
+ * <a href="#hexbins">``.hexbins()``</a>: Plots hex bins
93
+ * <a href="#histogram">``.hist()``</a>: Plots the distribution of one or histograms as a set of bins
94
+ * <a href="#kde-density">``.kde()``</a>: Plots the kernel density estimate of one or more variables.
95
+ * <a href="#the-plot-method">``.line()``</a>: Plots a line chart (such as for a time series)
96
+ * <a href="#scatter">``.scatter()``</a>: Plots a scatter chart comparing two variables
97
+ * <a href="#step">``.step()``</a>: Plots a step chart akin to a line plot
98
+ * <a href="#tables">``.table()``</a>: Generates a SlickGrid DataTable
99
+ * <a href="#groupby">``.violin()``</a>: Plots a violin plot comparing the distribution of one or more variables using the kernel density estimate
100
+
101
+ #### Area
102
+
103
+ Like most other plot types the ``area`` chart supports the three ways of defining a plot outlined above. An area chart is most useful when plotting multiple variables in a stacked chart. This can be achieve by specifying ``x``, ``y``, and ``by`` columns or using the ``columns`` and ``index``/``use_index`` (equivalent to ``x``) options:
104
+
105
+
106
+ ```python
107
+ crime.hvplot.area(x='Year', y=['Robbery', 'Aggravated assault'])
108
+ ```
109
+
110
+ We can also explicitly set ``stacked`` to False and define an ``alpha`` value to compare the values directly:
111
+
112
+
113
+ ```python
114
+ crime.hvplot.area(x='Year', y=['Aggravated assault', 'Robbery'], stacked=False, alpha=0.4)
115
+ ```
116
+
117
+ Another use for an area plot is to visualize the spread of a value. For instance using the flights dataset we may want to see the spread in mean delay values across carriers. For that purpose we compute the mean delay by day and carrier and then the min/max mean delay for across all carriers. Since the output of ``hvplot`` is just a regular holoviews object, we can use the overlay operator (\*) to place the plots on top of each other.
118
+
119
+
120
+ ```python
121
+ delay_min_max = flights.groupby(['day', 'carrier'])['carrier_delay'].mean().groupby('day').agg({'min': np.min, 'max': np.max})
122
+ delay_mean = flights.groupby('day')['carrier_delay'].mean()
123
+
124
+ delay_min_max.hvplot.area(x='day', y='min', y2='max', alpha=0.2) * delay_mean.hvplot()
125
+ ```
126
+
127
+ #### Bars
128
+
129
+ In the simplest case we can use ``.hvplot.bar`` to plot ``x`` against ``y``. We'll use ``rot=90`` to rotate the tick labels on the x-axis making the years easier to read:
130
+
131
+
132
+ ```python
133
+ crime.hvplot.bar(x='Year', y='Violent Crime rate', rot=90)
134
+ ```
135
+
136
+ If we want to compare multiple columns instead we can set ``y`` to a list of columns. Using the ``stacked`` option we can then compare the column values more easily:
137
+
138
+
139
+ ```python
140
+ crime.hvplot.bar(x='Year', y=['Violent crime total', 'Property crime total'],
141
+ stacked=True, rot=90, width=800, legend='top_left')
142
+ ```
143
+
144
+ #### Scatter
145
+
146
+ The scatter plot supports many of the same features as the other chart types we have seen so far but can also be colored by another variable using the ``c`` option.
147
+
148
+
149
+ ```python
150
+ crime.hvplot.scatter(x='Violent Crime rate', y='Burglary rate', c='Year')
151
+ ```
152
+
153
+ Anytime that color is being used to represent a dimension, the ``cmap`` option can be used to control the colormap that is used to represent that dimension. Additionally, the colorbar can be disabled using ``colorbar=False``.
154
+
155
+ #### Step
156
+
157
+ A step chart is very similar to a line chart but instead of linearly interpolating between samples the step chart visualizes discrete steps. The point at which to step can be controlled via the ``where`` keyword allowing `'pre'`, `'mid'` (default) and `'post'` values:
158
+
159
+
160
+ ```python
161
+ crime.hvplot.step(x='Year', y=['Robbery', 'Aggravated assault'])
162
+ ```
163
+
164
+ #### HexBins
165
+
166
+ You can create hexagonal bin plots with the ``hexbin`` method. Hexbin plots can be a useful alternative to scatter plots if your data are too dense to plot each point individually. Since these data are not regularly distributed, we'll use the ``logz`` option to map z-axis (color) to a log scale colorbar.
167
+
168
+
169
+ ```python
170
+ flights.hvplot.hexbin(x='airtime', y='arrdelay', width=600, height=500, logz=True)
171
+ ```
172
+
173
+ #### Bivariate
174
+
175
+ You can create a 2D density plot with the ``bivariate`` method. Bivariate plots can be a useful alternative to scatter plots if your data are too dense to plot each point individually.
176
+
177
+
178
+ ```python
179
+ crime.hvplot.bivariate(x='Violent Crime rate', y='Burglary rate', width=600, height=500)
180
+ ```
181
+
182
+ #### HeatMap
183
+
184
+ A ``HeatMap`` lets us view the relationship between three variables, so we specify the 'x' and 'y' variables and an additional 'C' variable. Additionally we can define a ``reduce_function`` that computes the values for each bin from the samples that fall into it. Here we plot the 'depdelay' (i.e. departure delay) for each day of the month and carrier in the dataset:
185
+
186
+
187
+ ```python
188
+ flights.compute().hvplot.heatmap(x='day', y='carrier', C='depdelay', reduce_function=np.mean, colorbar=True)
189
+ ```
190
+
191
+ #### Tables
192
+
193
+ Unlike all other plot types, a table only supports one signature: either all columns are plotted, or a subset of columns can be selected by defining the ``columns`` explicitly:
194
+
195
+
196
+ ```python
197
+ crime.hvplot.table(columns=['Year', 'Population', 'Violent Crime rate'], width=400)
198
+ ```
199
+
200
+ ### Distributions
201
+
202
+ Plotting distributions differs slightly from other plots since they plot only one variable in the simple case rather than plotting two or more variables against each other. Therefore when plotting these plot types no ``index`` or ``x`` value needs to be supplied. Instead:
203
+
204
+ 1. Declare a single ``y`` variable, e.g. ``source.plot.hist(variable)``, or
205
+ 2. Declare a ``y`` variable and ``by`` variable, e.g. ``source.plot.hist(variable, by='Group')``, or
206
+ 3. Declare columns or plot all columns, e.g. ``source.plot.hist()`` or ``source.plot.hist(columns=['A', 'B', 'C'])``
207
+
208
+ #### Histogram
209
+
210
+ The Histogram is the simplest example of a distribution; often we simply plot the distribution of a single variable, in this case the 'Violent Crime rate'. Additionally we can define a range over which to compute the histogram and the number of bins using the ``bin_range`` and ``bins`` arguments respectively:
211
+
212
+
213
+ ```python
214
+ crime.hvplot.hist(y='Violent Crime rate')
215
+ ```
216
+
217
+ Or we can plot the distribution of multiple columns:
218
+
219
+
220
+ ```python
221
+ columns = ['Violent Crime rate', 'Property crime rate', 'Burglary rate']
222
+ crime.hvplot.hist(y=columns, bins=50, alpha=0.5, legend='top', height=400)
223
+ ```
224
+
225
+ We can also group the data by another variable. Here we'll use ``subplots`` to split each carrier out into its own plot:
226
+
227
+
228
+ ```python
229
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
230
+ flight_subset.hvplot.hist('depdelay', by='carrier', bins=20, bin_range=(-20, 100), width=300, subplots=True)
231
+ ```
232
+
233
+ #### KDE (density)
234
+
235
+ You can also create density plots using ``hvplot.kde()`` or ``hvplot.density()``:
236
+
237
+
238
+ ```python
239
+ crime.hvplot.kde(y='Violent Crime rate')
240
+ ```
241
+
242
+ Comparing the distribution of multiple columns is also possible:
243
+
244
+
245
+ ```python
246
+ columns=['Violent Crime rate', 'Property crime rate', 'Burglary rate']
247
+ crime.hvplot.kde(y=columns, alpha=0.5, value_label='Rate', legend='top_right')
248
+ ```
249
+
250
+ The ``hvplot.kde`` also supports the ``by`` keyword:
251
+
252
+
253
+ ```python
254
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
255
+ flight_subset.hvplot.kde('depdelay', by='carrier', xlim=(-20, 70), width=300, subplots=True)
256
+ ```
257
+
258
+ #### Box-Whisker Plots
259
+
260
+ Just like the other distribution-based plot types, the box-whisker plot supports plotting a single column:
261
+
262
+
263
+ ```python
264
+ crime.hvplot.box(y='Violent Crime rate')
265
+ ```
266
+
267
+ It also supports multiple columns and the same options as seen previously (``legend``, ``invert``, ``value_label``):
268
+
269
+
270
+ ```python
271
+ columns=['Burglary rate', 'Larceny-theft rate', 'Motor vehicle theft rate',
272
+ 'Property crime rate', 'Violent Crime rate']
273
+ crime.hvplot.box(y=columns, group_label='Crime', legend=False, value_label='Rate (per 100k)', invert=True)
274
+ ```
275
+
276
+ Lastly, it also supports using the ``by`` keyword to split the data into multiple subsets:
277
+
278
+
279
+ ```python
280
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
281
+ flight_subset.hvplot.box('depdelay', by='carrier', ylim=(-10, 70))
282
+ ```
283
+
284
+ ## Composing Plots
285
+
286
+ One of the core strengths of HoloViews is the ease of composing
287
+ different plots. Individual plots can be composed using the ``*`` and
288
+ ``+`` operators, which overlay and compose plots into layouts
289
+ respectively. For more information on composing objects, see the
290
+ HoloViews [User Guide](https://holoviews.org/user_guide/Composing_Elements.html).
291
+
292
+ By using these operators we can combine multiple plots into composite plots. A simple example is overlaying two plot types:
293
+
294
+
295
+ ```python
296
+ crime.hvplot(x='Year', y='Violent Crime rate') * crime.hvplot.scatter(x='Year', y='Violent Crime rate', c='k')
297
+ ```
298
+
299
+ We can also lay out different plots and tables together:
300
+
301
+
302
+ ```python
303
+ (crime.hvplot.bar(x='Year', y='Violent Crime rate', rot=90, width=550) +
304
+ crime.hvplot.table(['Year', 'Population', 'Violent Crime rate'], width=420))
305
+ ```
306
+
307
+ ## Large data
308
+
309
+ The previous examples summarized the fairly large airline dataset using statistical plot types that aggregate the data into a feasible subset for plotting. We can instead aggregate the data directly into the viewable image using [datashader](https://datashader.org), which provides a rendering of the entire set of raw data available (as far as the resolution of the screen allows). Here we plot the 'airtime' against the 'distance':
310
+
311
+
312
+ ```python
313
+ flights.hvplot.scatter(x='distance', y='airtime', datashade=True)
314
+ ```
315
+
316
+ ## Groupby
317
+
318
+ Thanks to the ability of HoloViews to explore a parameter space with a set of widgets we can apply a groupby along a particular column or dimension. For example we can view the distribution of departure delays by carrier grouped by day, allowing the user to choose which day to display:
319
+
320
+
321
+ ```python
322
+ flights.hvplot.violin(y='depdelay', by='carrier', groupby='dayofweek', ylim=(-20, 60), height=500)
323
+ ```
324
+
325
+ This user guide merely provided an overview over the available plot types; to see a detailed description on how to customize plots see the [Customization](Customization.ipynb) user guide.
hvplot_docs/Plotting_Extensions.md ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ from hvplot.sample_data import us_crime
3
+ ```
4
+
5
+ hvPlot allows to generate plots with three different plotting extensions: [Bokeh](https://docs.bokeh.org), [Matplotlib](https://matplotlib.org/) and [Plotly](https://plotly.com/). Support for Maplotlib and Plotly was added in version 0.8.0, before which Bokeh was the only option available and is as such kept as the default plotting extension.
6
+
7
+ ## Loading plotting extensions
8
+
9
+ Importing hvPlot accessors as shown throughout the documentation (e.g. `import hvplot.pandas`, `import hvplot.xarray`, ...) loads by default the Bokeh plotting extension. The `extension` function can be used after this first import to additionally load the Matplotlib and Plotly plotting extensions, or just one of them. The currently active extension is the first one passed to `extension()`.
10
+
11
+
12
+ ```python
13
+ import hvplot.pandas # This import automatically loads the Bokeh extension.
14
+ ```
15
+
16
+
17
+ ```python
18
+ hvplot.extension('matplotlib', 'plotly') # This call loads the Matplotlib (the active one) and Plotly extensions.
19
+ ```
20
+
21
+
22
+ ```python
23
+ us_crime.hvplot(x='Year', y='Violent Crime rate')
24
+ ```
25
+
26
+ ## Switching the plotting extension
27
+
28
+ The `output` function allows to switch the plotting extension by setting the `backend` parameter. It must be called **after** importing an accessor (e.g. `import hvplot.pandas`) and calling to `extension`:
29
+
30
+
31
+ ```python
32
+ hvplot.output(backend='plotly')
33
+
34
+ us_crime.hvplot(x='Year', y='Violent Crime rate')
35
+ ```
36
+
37
+ <div class="alert alert-warning" role="alert">
38
+ The <code>extension</code> function could also be used to switch the plotting extension. You should however prefer <code>output</code> as any call to <code>extension</code> actually internally loads code in the output of the cell where it is executed, which can significantly increase the notebook size if <code>extension</code> is called in multiple cells.
39
+ </div>
40
+
41
+ ## Plot styling options
42
+
43
+ On top of the parameters part of `.hvplot()`'s API, a plot can be further customized by providing detailed styling options. By default, i.e. without calling `hvplot.extension()`, the accepted styling options are those of Bokeh, such as `line_dash='dashed'`. When another extension is set with either `extension` or `output` the styling options of that extension are expected, e.g. `linestyle='dashed'` for Matplotlib.
44
+
45
+
46
+ ```python
47
+ hvplot.output(backend='bokeh')
48
+ us_crime.hvplot(x='Year', y='Violent Crime rate', line_dash='dashed')
49
+ ```
50
+
51
+
52
+ ```python
53
+ hvplot.output(backend='matplotlib')
54
+ us_crime.hvplot(x='Year', y='Violent Crime rate', linestyle='dashed')
55
+ ```
56
+
57
+ It is also possible to generates plots with either Matplotlib or Plotly that are constructed with Bokeh options. This can be used for instance to create plots previously generated with Bokeh with another backend, without having to change any parameter. The only change required is to declare Bokeh as the compatible extension with `hvplot.extension('matplotlib', compatibility='bokeh')`, preferably at the beginning of the notebook.
58
+
59
+
60
+ ```python
61
+ hvplot.extension('matplotlib', compatibility='bokeh')
62
+ violent_crime = us_crime.hvplot(x='Year', y='Violent Crime rate', line_dash='dashed')
63
+ violent_crime
64
+ ```
65
+
66
+
67
+ ```python
68
+ violent_crime.opts.info()
69
+ ```
70
+
71
+ You can see that `line_dash='dashed'` has been internally converted to `linestyle='dashed'` that is a valid option for the Matplotlib plotting extension. Note that support for all Bokeh options is currently incomplete and will continue to be improved while equivalent support for Plotly and Matplotlib is planned for a future release
72
+
73
+ ## Obtaining the underlying figure object
74
+
75
+ In some cases it can be convenient to construct a plot with hvPlot and then get a handle on the figure object of the underlying plotting library to further customize the plot or to embed it in some more complex application. The `render` function allows to get a handle on the figure object. The following examples show that it's possible to use the API of Bokeh, Matplotlib or Plotly to update the title of the `violent_crime` plot.
76
+
77
+
78
+ ```python
79
+ violent_crime = us_crime.hvplot(x='Year', y='Violent Crime rate')
80
+ ```
81
+
82
+
83
+ ```python
84
+ from bokeh.io import show
85
+
86
+ bk_fig = hvplot.render(violent_crime, backend='bokeh')
87
+ bk_fig.title = 'Violent crime'
88
+ show(bk_fig)
89
+ ```
90
+
91
+
92
+ ```python
93
+ %matplotlib inline
94
+ mpl_fig = hvplot.render(violent_crime, backend='matplotlib')
95
+ axes = mpl_fig.get_axes()
96
+ axes[0].set_title('Violent crime')
97
+ mpl_fig
98
+ ```
99
+
100
+
101
+ ```python
102
+ from plotly.graph_objects import Figure
103
+
104
+ plotly_fig = hvplot.render(violent_crime, backend='plotly')
105
+ fig = Figure(plotly_fig).update_layout(title='Violent crime')
106
+ fig
107
+ ```
108
+
109
+ As demonstrated on this page hvPlot offers you the ability to choose your favorite plotting extension among those supported by [HoloViews](https://holoviews.org/), on which hvPlot is built.
hvplot_docs/Plotting_with_Bokeh.md ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Plotting with Bokeh
2
+
3
+
4
+ ```python
5
+ import numpy as np
6
+ import pandas as pd
7
+ import holoviews as hv
8
+ from holoviews import dim, opts
9
+
10
+ hv.extension('bokeh')
11
+ ```
12
+
13
+ One of the major design principles of HoloViews is that the declaration of data is completely independent from the plotting implementation. Bokeh provides a powerful platform to generate interactive plots using HTML5 canvas and WebGL, and is ideally suited towards interactive exploration of data. By combining the ease of generating interactive, high-dimensional visualizations with the interactive widgets and fast rendering provided by Bokeh, HoloViews becomes even more powerful.
14
+
15
+ This user guide will cover various interactive features that bokeh provides which is not covered by the more general user guides, including interactive tools, linked axes and brushing and more. The general principles behind customizing plots and styling the visual elements of a plot are covered in the [Style Mapping](04-Style_Mapping.ipynb) and [Customizing Plots](Customizing_Plots.ipynb) user guides.
16
+
17
+ ## Working with bokeh directly
18
+
19
+ When HoloViews outputs bokeh plots it creates and manipulates bokeh models in the background. If at any time you need access to the underlying Bokeh representation of an object you can use the ``hv.render`` function to convert it. For example let us convert a HoloViews ``Image`` to a bokeh Figure, which will let us access and modify every aspect of the plot:
20
+
21
+
22
+ ```python
23
+ img = hv.Image(np.random.rand(10, 10))
24
+
25
+ fig = hv.render(img)
26
+
27
+ print('Figure: ', fig)
28
+ print('Renderers: ', fig.renderers[-1].glyph)
29
+ ```
30
+
31
+ ## Exporting static files
32
+
33
+ Bokeh supports static export to png's using ``Selenium``, ``PhantomJS`` and ``pillow``, to install the required dependencies run:
34
+
35
+ ```
36
+ conda install selenium phantomjs pillow
37
+ ```
38
+
39
+ alternatively install PhantomJS from npm using:
40
+
41
+ ```
42
+ npm install -g phantomjs-prebuilt
43
+ ```
44
+
45
+ To switch to png output persistently you can run:
46
+
47
+ ```
48
+ hv.output(fig='png')
49
+ ```
50
+
51
+
52
+ ```python
53
+ violin = hv.Violin(np.random.randn(100))
54
+
55
+ hv.output(violin, fig='png')
56
+ ```
57
+
58
+ The exported png can also be saved to disk using the ``save`` function by changing the file extension from ``.html``, which exports an interactive plot, ``.png``:
59
+
60
+ ```python
61
+ hv.save(violin, 'violin.png')
62
+ ```
63
+
64
+ ## Element Style options
65
+
66
+ One of the major benefits of bokeh is that it was designed from the ground up with consistency in mind, therefore most style options are a combination of the fill, line, and text style options listed below:
67
+
68
+
69
+ ```python
70
+ from holoviews.plotting.bokeh.styles import (line_properties, fill_properties, text_properties)
71
+ print("""
72
+ Line properties: %s\n
73
+ Fill properties: %s\n
74
+ Text properties: %s
75
+ """ % (line_properties, fill_properties, text_properties))
76
+ ```
77
+
78
+ Note also that most of these options support vectorized style mapping as described in the [Style Mapping user guide](04-Style_Mapping.ipynb). Here's an example of HoloViews Elements using a Bokeh backend, with bokeh's style options:
79
+
80
+
81
+ ```python
82
+ curve_opts = opts.Curve(line_width=10, line_color='indianred', line_dash='dotted', line_alpha=0.5)
83
+ point_opts = opts.Points(fill_color='#00AA00', fill_alpha=0.5, line_width=1, line_color='black', size=5)
84
+ text_opts = opts.Text(text_align='center', text_baseline='middle', text_color='gray', text_font='Arial')
85
+
86
+ xs = np.linspace(0, np.pi*4, 100)
87
+ data = (xs, np.sin(xs))
88
+
89
+ (hv.Curve(data) + hv.Points(data) + hv.Text(6, 0, 'Here is some text')).opts(
90
+ curve_opts, point_opts, text_opts)
91
+ ```
92
+
93
+ Notice that because the first two plots use the same underlying data, they become linked, such that zooming or panning one of the plots makes the corresponding change on the other.
94
+
95
+ ## Setting Backend Opts
96
+
97
+ HoloViews does not expose every single option from Bokeh.
98
+
99
+ Instead, HoloViews allow users to attach [hooks](Customizing_Plots.ipynb#plot-hooks) to modify the plot object directly--but writing these hooks could be cumbersome, especially if it's only used for a single line of update.
100
+
101
+ Fortunately, HoloViews allows `backend_opts` for the Bokeh backend to configure options by declaring a dictionary with accessor specification for updating the plot components.
102
+
103
+ For example, here's how to make the toolbar auto-hide.
104
+
105
+
106
+ ```python
107
+ hv.Curve(data).opts(
108
+ backend_opts={"plot.toolbar.autohide": True}
109
+ )
110
+ ```
111
+
112
+ The following is the equivalent, as a hook.
113
+
114
+
115
+ ```python
116
+ def hook(hv_plot, element):
117
+ toolbar = hv_plot.handles['plot'].toolbar
118
+ toolbar.autohide = True
119
+
120
+ hv.Curve(data).opts(hooks=[hook])
121
+ ```
122
+
123
+ Notice how much more concise it is with `backend_opts`!
124
+
125
+ With knowledge of the attributes of Bokeh, it's possible to configure many other plot components besides `toolbar`. Some examples include `legend`, `colorbar`, `xaxis`, `yaxis`, and much, much more.
126
+
127
+ If you're unsure, simply input your best guess and it'll try to provide a list of suggestions if there's an issue.
128
+
129
+ ## Sizing Elements
130
+
131
+ In the bokeh backend the sizing of plots and specifically layouts of plots is determined in an inside-out or compositional manner. Each subplot can be sized independently and it will fill the allocated space. The sizing is determined by the combination of width, height, aspect and responsive options. In this section we will discover the different approaches to setting plot dimensions and aspects.
132
+
133
+ ### Width and height
134
+
135
+ The most straightforward approach to specifying the size of a plot is using a width and height specified in pixels. This is the default behavior and makes it easy to achieve precise alignment between plots but does not allow for keeping a constant aspect or responsive resizing. In particular, the specified size includes the axes and any legends or colorbars.
136
+
137
+
138
+ ```python
139
+ points_a = hv.Points(data)
140
+ points_b = hv.Points(data)
141
+
142
+ points_a.opts(width=300, height=300) + points_b.opts(width=600, height=300)
143
+ ```
144
+
145
+ ### Frame width and height
146
+
147
+ The frame width on the other hand provides precise control over the inner dimensions of a plot, it ensures the actual plot frame matches the specified dimensions exactly. This makes it possible to achieve a precise aspect ratio between the axis scales, without worrying about the size of axes, colorbars, titles and legends, e.g. below we can see two plots defined using explicit ``frame_width`` and ``frame_height`` share the same dimensions despite the fact that one has a colorbar.
148
+
149
+
150
+ ```python
151
+ xs = ys = np.arange(10)
152
+ yy, xx = np.meshgrid(xs, ys)
153
+ zz = xx*yy
154
+
155
+ img = hv.Image(np.random.rand(100, 100))
156
+
157
+ points_a.opts(frame_width=200, frame_height=200) +\
158
+ img.opts(frame_width=200, frame_height=200, colorbar=True, axiswise=True)
159
+ ```
160
+
161
+ ### Aspect
162
+
163
+ The ``aspect`` and ``data_aspect`` options provide control over the scaling of the plot dimensions and the axis limits.
164
+
165
+ #### ``aspect``:
166
+
167
+ The ``aspect`` specifies the ratio between the width and height dimensions of the plot. If specified this options takes absolute precedence over the dimensions of the plot but has no effect on the plot's axis limits. It supports the following options:
168
+
169
+ * **``float``** : A numeric value will scale the ratio of plot width to plot height
170
+ * **``"equal"``** : Sets aspect of the axis scaling to be equal, equivalent to **``data_aspect=1``**
171
+ * **``"square"``** : Ensures the plot dimensions are square.
172
+
173
+ #### ``data_aspect``:
174
+
175
+ The ``data_aspect`` specifies the scaling between the x- and y-axis ranges. If specified this option will scale both the plot ranges and dimensions unless explicit ``aspect``, ``width`` or ``height`` value overrides the plot dimensions:
176
+
177
+ * **``float``** : Sets ratio between the units on the x-scale and the y-scale
178
+
179
+
180
+ ```python
181
+ xs = np.linspace(0, 10)
182
+ ys = np.linspace(0, 5)
183
+
184
+ img = hv.Image((xs, ys, xs[:, np.newaxis]*np.sin(ys*4)))
185
+
186
+ (img.options(aspect='equal').relabel('aspect=\'equal\'') +
187
+ img.options(aspect='square', colorbar=True, frame_width=300).relabel('aspect=\'square\'') +
188
+ img.options(aspect=2).relabel('aspect=2') +
189
+ img.options(data_aspect=2, frame_width=300).relabel('data_aspect=2')).cols(2)
190
+ ```
191
+
192
+ ## Responsive
193
+
194
+ Since bokeh plots are rendered within a browser window which can be resized dynamically it supports responsive sizing modes allowing the plot to rescale when the window it is placed in is changed. If enabled, the behavior of ``responsive`` modes depends on whether an aspect or width/height option is set. Specifically responsive mode will only work if at least one dimension of the plot is left undefined, e.g. when width and height or width and aspect are set the plot is set to a fixed size, ignoring any ``responsive`` option. This leaves four different ``responsive`` modes:
195
+
196
+ * **``scale_both``**: If neither a width or a height are defined but a fixed aspect is defined both axes will be scaled up to the maximum size of the container. Scaling ensures that the aspect ratio of the plot is maintained.
197
+ * **``stretch_both``**: If neither a width, height or aspect are defined the plot will stretch to fill all available space.
198
+ * **``stretch_width``**: If a height but neither a width or aspect are defined the plot will stretch to fill all available horizontal space.
199
+ * **``stretch_height``**: If a width but neither a height or aspect are defined the plot will stretch to fill all available vertical space.
200
+
201
+ **Note**: In the notebook stretching and scaling the height does not increase the size of a cell.
202
+
203
+ As a simple example let us declare a plot that has a fixed height but stretches to fit all available horizontal space:
204
+
205
+
206
+ ```python
207
+ hv.Points(data).opts(height=200, responsive=True, title='stretch width')
208
+ ```
209
+
210
+ Similarly if we declare a fixed ``aspect`` or ``data_aspect`` responsive modes will try to fill all available space but avoid distorting the specified aspect:
211
+
212
+
213
+ ```python
214
+ img.opts(data_aspect=0.5, responsive=True, title='scale both')
215
+ ```
216
+
217
+ ## Alignment
218
+
219
+ The alignment of a plot in a row or column can be controlled using the ``align`` option. It controls both the vertical alignment in a row and the horizontal alignment in a column and can be set to one of `'start'`, `'center'` or `'end'` (where `'start'` is the default).
220
+
221
+ #### Vertical
222
+
223
+
224
+ ```python
225
+ points = hv.Points(data).opts(axiswise=True)
226
+ img = hv.Image((xs, ys, xs[:, np.newaxis]*np.sin(ys*4)))
227
+
228
+ img + points.opts(height=200, align='end')
229
+ ```
230
+
231
+ #### Horizontal
232
+
233
+
234
+ ```python
235
+ (img.opts(axiswise=True, width=200, align='center') + points).cols(1)
236
+ ```
237
+
238
+ ## Grid lines
239
+
240
+ Grid lines can be controlled through the combination of ``show_grid`` and ``gridstyle`` parameters. The ``gridstyle`` allows specifying a number of options including:
241
+
242
+ * ``grid_line_color``
243
+ * ``grid_line_alpha``
244
+ * ``grid_line_dash``
245
+ * ``grid_line_width``
246
+ * ``grid_bounds``
247
+ * ``grid_band``
248
+
249
+ These options may also be applied to minor grid lines by prepending the ``'minor_'`` prefix and may be applied to a specific axis by replacing ``'grid_`` with ``'xgrid_'`` or ``'ygrid_'``. Here we combine some of these options to generate a complex grid pattern:
250
+
251
+
252
+ ```python
253
+ grid_style = {'grid_line_color': 'black', 'grid_line_width': 1.5, 'ygrid_bounds': (0.3, 0.7),
254
+ 'minor_xgrid_line_color': 'lightgray', 'xgrid_line_dash': [4, 4]}
255
+
256
+ hv.Points(np.random.rand(10, 2)).opts(gridstyle=grid_style, show_grid=True, size=5, width=600)
257
+ ```
258
+
259
+ ## Containers
260
+
261
+ The bokeh plotting extension also supports a number of additional features relating to container components.
262
+
263
+ ### Tabs
264
+
265
+ Using bokeh, both ``(Nd)Overlay`` and ``(Nd)Layout`` types may be displayed inside a ``tabs`` widget. This may be enabled via a plot option ``tabs``, and may even be nested inside a Layout.
266
+
267
+
268
+ ```python
269
+ x,y = np.mgrid[-50:51, -50:51] * 0.1
270
+
271
+ img = hv.Image(np.sin(x**2+y**2), bounds=(-1,-1,1,1))
272
+ (img.relabel('Image') * img.sample(x=0).relabel('Cross-section')).opts(tabs=True)
273
+ ```
274
+
275
+ Another reason to use ``tabs`` is that some Layout combinations may not be able to be displayed directly using HoloViews. For example, it is not currently possible to display a ``GridSpace`` as part of a ``Layout`` in any backend, and this combination will automatically switch to a ``tab`` representation for the bokeh backend.
276
+
277
+ ### Interactive Legends
278
+
279
+
280
+
281
+ When using ``NdOverlay`` and ``Overlay`` containers each element will get a legend entry, which can be used to interactively toggle the visibility of the element. In this example we will create a number of ``Histogram`` elements each with a different mean. By setting a ``muted_fill_alpha`` we can define the style of the element when it is de-selected using the legend, simply try tapping on each legend entry to see the effect:
282
+
283
+
284
+ ```python
285
+ hv.NdOverlay({i: hv.Histogram(np.histogram(np.random.randn(100)+i*2)) for i in range(5)}).opts(
286
+ 'Histogram', width=600, alpha=0.8, muted_fill_alpha=0.1)
287
+ ```
288
+
289
+ The other ``muted_`` options can be used to define other aspects of the Histogram style when it is unselected.
290
+
291
+ If you have multiple plots in a ``Layout`` with the same legend label, muting one of them will automatically mute all of them.
292
+
293
+
294
+ ```python
295
+ overlay1 = hv.Curve([0, 0], label="A") * hv.Curve([1, 1], label="B")
296
+ overlay2 = hv.Curve([2, 2], label="A") * hv.Curve([3, 3], label="B")
297
+ layout = overlay1 + overlay2
298
+ layout
299
+ ```
300
+
301
+ If you want to turn off this behavior, use ``.opts(sync_legends=False)``
302
+
303
+
304
+ ```python
305
+ layout.opts(sync_legends=False)
306
+ ```
307
+
308
+ If you want to control the number of legend shown in the ``Layout`` or the position of them ``show_legends`` and ``legend_position`` can be used.
309
+
310
+
311
+ ```python
312
+ layout.opts(sync_legends=True, show_legends=0, legend_position="top_left")
313
+ ```
314
+
315
+ ### Marginals
316
+
317
+
318
+ The Bokeh backend also supports marginal plots to generate adjoined plots. The most convenient way to build an AdjointLayout is with the ``.hist()`` method.
319
+
320
+
321
+ ```python
322
+ points = hv.Points(np.random.randn(500,2))
323
+ points.hist(num_bins=51, dimension=['x','y'])
324
+ ```
325
+
326
+ When the histogram represents a quantity that is mapped to a value dimension with a corresponding colormap, it will automatically share the colormap, making it useful as a colorbar for that dimension as well as a histogram.
327
+
328
+
329
+ ```python
330
+ img.hist(num_bins=100, dimension=['x', 'y'], weight_dimension='z', mean_weighted=True) +\
331
+ img.hist(dimension='z')
332
+ ```
333
+
334
+ ## Tools
335
+
336
+ Bokeh provides a range of tools to interact with a plot and HoloViews adds a number of tools by default but also makes it easy to add additional tools. The ``default_tools`` define the list of tools that are added automatically and usually a user would override only the ``tools`` option to add additional tools. By default the ``default_tools`` include:
337
+
338
+ ['save', 'pan', 'wheel_zoom', 'box_zoom', 'reset']
339
+
340
+ #### Toolbar
341
+
342
+ The bokeh toolbar is added automatically and will be placed to the right for a single plot and on the top for a layout. Additionally for layouts of plots it will automatically merge the toolbars to avoid crowding the plot. However both behaviors can be customized, the toolbar can be hidden or moved to a different location on a plot by setting the ``toolbar`` option to one of:
343
+
344
+ ['above', 'below', 'left', 'right', 'disable', None]
345
+
346
+ Secondly a layout or grid plot supports the ``merge_tools`` option which can be used to maintain one toolbar per plot:
347
+
348
+
349
+ ```python
350
+ (hv.Curve([1, 2, 3]).opts(toolbar='above') + hv.Curve([1, 2, 3]).opts(toolbar=None)).opts(merge_tools=False)
351
+ ```
352
+
353
+ #### Hover tools
354
+
355
+ Some Elements allow revealing additional data by hovering over the data. To enable the hover tool, simply supply ``'hover'`` as a list to the ``tools`` plot option. By default the tool will display information for all the dimensions specified on the element:
356
+
357
+
358
+ ```python
359
+ error = np.random.rand(100, 3)
360
+ heatmap_data = {(chr(65+i), chr(97+j)):i*j for i in range(5) for j in range(5) if i!=j}
361
+ data = [np.random.normal() for i in range(10000)]
362
+ hist = np.histogram(data, 20)
363
+
364
+ points = hv.Points(error)
365
+ heatmap = hv.HeatMap(heatmap_data).sort()
366
+ histogram = hv.Histogram(hist)
367
+ image = hv.Image(np.random.rand(50,50))
368
+
369
+ (points + heatmap + histogram + image).opts(
370
+ opts.Points(tools=['hover'], size=5), opts.HeatMap(tools=['hover']),
371
+ opts.Image(tools=['hover']), opts.Histogram(tools=['hover']),
372
+ opts.Layout(shared_axes=False)).cols(2)
373
+ ```
374
+
375
+ Additionally, you can provide `'vline'`, the equivalent of passing `HoverTool(mode='vline')`, or `'hline'` to set the hit-testing behavior
376
+
377
+
378
+ ```python
379
+ error = np.random.rand(100, 3)
380
+ heatmap_data = {(chr(65+i), chr(97+j)):i*j for i in range(5) for j in range(5) if i!=j}
381
+ data = [np.random.normal() for i in range(10000)]
382
+ hist = np.histogram(data, 20)
383
+
384
+ points = hv.Points(error)
385
+ heatmap = hv.HeatMap(heatmap_data).sort()
386
+ histogram = hv.Histogram(hist)
387
+ image = hv.Image(np.random.rand(50,50))
388
+
389
+ (points + heatmap + histogram + image).opts(
390
+ opts.Points(tools=['hline'], size=5), opts.HeatMap(tools=['hover']),
391
+ opts.Image(tools=['vline']), opts.Histogram(tools=['hover']),
392
+ opts.Layout(shared_axes=False)).cols(2)
393
+ ```
394
+
395
+ It is also possible to explicitly declare the columns to display by manually constructing a `HoverTool` and declaring the tooltips as a list of tuples of the labels and a specification of the dimension name and how to display it (for a complete reference see the [bokeh user guide](https://bokeh.pydata.org/en/latest/docs/user_guide/tools.html#hovertool)).
396
+
397
+
398
+ ```python
399
+ from bokeh.models import HoverTool
400
+ from bokeh.sampledata.periodic_table import elements
401
+
402
+ points = hv.Points(
403
+ elements, ['electronegativity', 'density'],
404
+ ['name', 'symbol', 'metal', 'CPK', 'atomic radius']
405
+ ).sort('metal')
406
+
407
+ tooltips = [
408
+ ('Name', '@name'),
409
+ ('Symbol', '@symbol'),
410
+ ('CPK', '$color[hex, swatch]:CPK')
411
+ ]
412
+ hover = HoverTool(tooltips=tooltips)
413
+
414
+ points.opts(
415
+ tools=[hover], color='metal', cmap='Category20',
416
+ line_color='black', size=dim('atomic radius')/10,
417
+ width=600, height=400, show_grid=True,
418
+ title='Chemical Elements by Type (scaled by atomic radius)')
419
+ ```
420
+
421
+ #### Selection tools
422
+
423
+ Bokeh provides a number of tools for selecting data points including ``tap``, ``box_select``, ``lasso_select`` and ``poly_select``. To distinguish between selected and unselected data points we can also control the color and alpha of the ``selection`` and ``nonselection`` points. You can try out any of these selection tools and see how the plot is affected:
424
+
425
+
426
+ ```python
427
+ hv.Points(error).opts(
428
+ color='blue', nonselection_color='red', size=10, tools=['box_select', 'lasso_select', 'tap'])
429
+ ```
430
+
431
+ #### Selection tool with shared axes and linked brushing
432
+
433
+ When dealing with complex multi-variate data it is often useful to explore interactions between variables across plots. HoloViews will automatically link the data sources of plots in a Layout if they draw from the same data, allowing for both linked axes and brushing.
434
+
435
+ We'll see what this looks like in practice using a small dataset of macro-economic data:
436
+
437
+
438
+ ```python
439
+ macro_df = pd.read_csv('http://assets.holoviews.org/macro.csv', sep='\t')
440
+ ```
441
+
442
+ By creating two ``Points`` Elements, which both draw their data from the same pandas DataFrame, the two plots become automatically linked. Note that the [Linking Plots user guide](Linking_Plots.ipynb) provides a more explicit way to declare two elements as being linked without having to share the same underlying datastructure. The automated linking behavior can be toggled with the ``shared_datasource`` plot option on a ``Layout`` or ``GridSpace``. You can try selecting data in one plot, and see how the corresponding data (those on the same rows of the DataFrame, even if the plots show different data, will be highlighted in each.
443
+
444
+
445
+ ```python
446
+ (hv.Scatter(macro_df, 'year', 'gdp') + hv.Scatter(macro_df, 'gdp', 'unem')).opts(
447
+ opts.Scatter(tools=['box_select', 'lasso_select']), opts.Layout(shared_axes=True, shared_datasource=True))
448
+ ```
449
+
450
+ A gridmatrix is a clear use case for linked plotting. This operation plots any combination of numeric variables against each other, in a grid, and selecting datapoints in any plot will highlight them in all of them. Such linking can thus reveal how values in a particular range (e.g. very large outliers along one dimension) relate to each of the other dimensions.
451
+
452
+
453
+ ```python
454
+ table = hv.Dataset(macro_df, kdims=['year', 'country'])
455
+ matrix = hv.operation.gridmatrix(table.groupby('country'))
456
+
457
+ matrix.select(country=['West Germany', 'United Kingdom', 'United States']).opts(
458
+ opts.Scatter(tools=['box_select', 'lasso_select', 'hover'], border=0))
459
+ ```
460
+
461
+ #### Drawing Tools
462
+
463
+ Another commonly useful set of tools are the drawing tools which are integrated with the linked streams introduced in the [Custom Interactivity guide](13-Custom_Interactivity.ipynb). These tools allow drawing and annotating a plot and accessing the annotation back in Python. The available drawing tools include:
464
+
465
+ * [PointDraw](../reference/streams/bokeh/PointDraw.ipynb): The ``PointDraw`` stream adds a bokeh tool to the source plot, which allows drawing, dragging and deleting points.
466
+ * [BoxEdit](../reference/streams/bokeh/BoxEdit.ipynb): The ``BoxEdit`` stream adds a bokeh tool to the source plot, which allows drawing, dragging and deleting boxes.
467
+ * [FreehandDraw](../reference/streams/bokeh/FreehandDraw.ipynb): The ``FreehandDraw`` stream adds a bokeh tool to the source plot, which allows freehand drawing on the plot canvas
468
+ * [PolyDraw](../reference/streams/bokeh/PolyDraw.ipynb): The ``PolyDraw`` stream adds a bokeh tool to the source plot, which allows drawing, dragging and deleting polygons and paths.
469
+ * [PolyEdit](../reference/streams/bokeh/PolyEdit.ipynb): The ``PolyEdit`` stream adds a bokeh tool to the source plot, which allows drawing, dragging and deleting vertices on polygons and paths.
470
+
471
+ Each of the reference notebooks explains the tools in more detail but to get an of how these tools work see the ``FreehandDraw`` example below, which allows drawing on the canvas and accessing the drawn data from Python:
472
+
473
+
474
+ ```python
475
+ path = hv.Path([])
476
+ freehand = hv.streams.FreehandDraw(source=path, num_objects=3)
477
+
478
+ path.opts(
479
+ opts.Path(active_tools=['freehand_draw'], height=400, line_width=10, width=400))
480
+ ```
481
+
482
+ To access the data from Python, you can access the ``element`` property on the stream, which lets us access each drawn line drawn as a separate ``Path`` element:
483
+
484
+
485
+ ```python
486
+ freehand.element.split()
487
+ ```
488
+
489
+ The [Reference Gallery](http://holoviews.org/reference/index.html) shows examples of all the Elements supported for Bokeh, in a format that can be compared with the corresponding matplotlib versions.
490
+
491
+ ## Theming
492
+
493
+ Bokeh supports theming via the [Theme object](https://bokeh.pydata.org/en/latest/docs/reference/themes.html) object which can also be using in HoloViews. Applying a Bokeh theme is useful when you need to set detailed aesthetic options not directly exposed via the HoloViews style options.
494
+
495
+ To apply a Bokeh theme, you will need to create a ``Theme`` object:
496
+
497
+
498
+ ```python
499
+ from bokeh.themes.theme import Theme
500
+
501
+ theme = Theme(
502
+ json={
503
+ 'attrs' : {
504
+ 'Figure' : {
505
+ 'background_fill_color': '#2F2F2F',
506
+ 'border_fill_color': '#2F2F2F',
507
+ 'outline_line_color': '#444444',
508
+ },
509
+ 'Grid': {
510
+ 'grid_line_dash': [6, 4],
511
+ 'grid_line_alpha': .3,
512
+ },
513
+
514
+ 'Axis': {
515
+ 'major_label_text_color': 'white',
516
+ 'axis_label_text_color': 'white',
517
+ 'major_tick_line_color': 'white',
518
+ 'minor_tick_line_color': 'white',
519
+ 'axis_line_color': "white"
520
+ }
521
+ }
522
+ })
523
+ ```
524
+
525
+ Instead of supplying a JSON object, you can also create a Bokeh ``Theme`` object from a [YAML file](https://bokeh.pydata.org/en/latest/docs/reference/themes.html). Once the ``Theme`` object is created, you can apply it by setting it on the ``theme`` parameter of the current Bokeh renderer:
526
+
527
+
528
+ ```python
529
+ hv.renderer('bokeh').theme = theme
530
+ ```
531
+
532
+ The theme will then be applied to subsequent plots:
533
+
534
+
535
+ ```python
536
+ xs = np.linspace(0, np.pi*4, 100)
537
+ hv.Curve((xs, np.sin(xs)), label='foo').opts(bgcolor='grey')
538
+ ```
539
+
540
+ You may also supply a name from Bokeh's built-in-themes:
541
+
542
+
543
+ ```python
544
+ hv.renderer('bokeh').theme = 'light_minimal'
545
+ xs = np.linspace(0, np.pi*4, 100)
546
+ hv.Curve((xs, np.sin(xs)), label='foo')
547
+ ```
548
+
549
+ To disable theming, you can set the ``theme`` parameter on the Bokeh renderer to ``None``.
hvplot_docs/Plotting_with_Matplotlib.md ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This page demonstrates the use of the **Matplotlib** plotting backend, the equivalent page demonstrating the Plotly backend may be found [here](Plotting_with_Plotly.ipynb)
2
+
3
+ As we discovered in the [Introduction](Introduction.ipynb), HoloViews allows plotting a variety of data types. Here we will use the sample data module and load the pandas and dask hvPlot API:
4
+
5
+
6
+ ```python
7
+ import numpy as np
8
+ import hvplot.pandas # noqa
9
+ import hvplot.dask # noqa
10
+
11
+ hvplot.extension('matplotlib')
12
+ ```
13
+
14
+ As we learned the hvPlot API closely mirrors the [Pandas plotting API](https://pandas.pydata.org/pandas-docs/stable/visualization.html), but instead of generating static images when used in a notebook, it uses HoloViews to generate either static or dynamically streaming Bokeh plots. Static plots can be used in any context, while streaming plots require a live [Jupyter notebook](https://jupyter.org), a deployed [Bokeh Server app](https://bokeh.pydata.org/en/latest/docs/user_guide/server.html), or a deployed [Panel](https://panel.pyviz.org) app.
15
+
16
+ HoloViews provides an extensive, very rich set of objects along with a powerful set of operations to apply, as you can find out in the [HoloViews User Guide](https://holoviews.org/user_guide/index.html). But here we will focus on the most essential mechanisms needed to make your data visualizable, without having to worry about the mechanics going on behind the scenes.
17
+
18
+ We will be focusing on two different datasets:
19
+
20
+ - A small CSV file of US crime data, broken down by state
21
+ - A larger Parquet-format file of airline flight data
22
+
23
+ The ``hvplot.sample_data`` module makes these datasets Intake data catalogue, which we can load either using pandas:
24
+
25
+
26
+ ```python
27
+ from hvplot.sample_data import us_crime, airline_flights
28
+
29
+ crime = us_crime.read()
30
+ print(type(crime))
31
+ crime.head()
32
+ ```
33
+
34
+ Or using dask as a ``dask.DataFrame``:
35
+
36
+
37
+ ```python
38
+ flights = airline_flights.to_dask().persist()
39
+ print(type(flights))
40
+ flights.head()
41
+ ```
42
+
43
+ ## The plot interface
44
+
45
+ The ``dask.dataframe.DataFrame.hvplot``, ``pandas.DataFrame.hvplot`` and ``intake.DataSource.plot`` interfaces (and Series equivalents) from HvPlot provide a powerful high-level API to generate complex plots. The ``.hvplot`` API can be called directly or used as a namespace to generate specific plot types.
46
+
47
+ ### The plot method
48
+
49
+ The most explicit way to use the plotting API is to specify the names of columns to plot on the ``x``- and ``y``-axis respectively:
50
+
51
+
52
+ ```python
53
+ crime.hvplot.line(x='Year', y='Violent Crime rate')
54
+ ```
55
+
56
+ As you'll see in more detail below, you can choose which kind of plot you want to use for the data:
57
+
58
+
59
+ ```python
60
+ crime.hvplot(x='Year', y='Violent Crime rate', kind='scatter')
61
+ ```
62
+
63
+ To group the data by one or more additional columns, specify an additional ``by`` variable. As an example here we will plot the departure delay ('depdelay') as a function of 'distance', grouping the data by the 'carrier'. There are many available carriers, so we will select only two of them so that the plot is readable:
64
+
65
+
66
+ ```python
67
+ flight_subset = flights[flights.carrier.isin(['OH', 'F9'])]
68
+ flight_subset.hvplot(x='distance', y='depdelay', by='carrier', kind='scatter', alpha=0.2, persist=True)
69
+ ```
70
+
71
+ Here we have specified the `x` axis explicitly, which can be omitted if the Pandas index column is already the desired x axis. Similarly, here we specified the `y` axis; by default all of the non-index columns would be plotted (which would be a lot of data in this case). If you don't specify the 'y' axis, it will have a default label named 'value', but you can then provide a y axis label explicitly using the ``value_label`` option.
72
+
73
+ Putting all of this together we will plot violent crime, robbery, and burglary rates on the y-axis, specifying 'Year' as the x, and relabel the y-axis to display the 'Rate'.
74
+
75
+
76
+ ```python
77
+ crime.hvplot(x='Year', y=['Violent Crime rate', 'Robbery rate', 'Burglary rate'],
78
+ value_label='Rate (per 100k people)')
79
+ ```
80
+
81
+ ### The hvplot namespace
82
+
83
+ Instead of using the ``kind`` argument to the plot call, we can use the ``hvplot`` namespace, which lets us easily discover the range of plot types that are supported. Use tab completion to explore the available plot types:
84
+
85
+ ```python
86
+ crime.hvplot.<TAB>
87
+ ```
88
+
89
+ Plot types available include:
90
+
91
+ * <a href="#area">``.area()``</a>: Plots a area chart similar to a line chart except for filling the area under the curve and optionally stacking
92
+ * <a href="#bars">``.bar()``</a>: Plots a bar chart that can be stacked or grouped
93
+ * <a href="#bivariate">``.bivariate()``</a>: Plots 2D density of a set of points
94
+ * <a href="#box-whisker-plots">``.box()``</a>: Plots a box-whisker chart comparing the distribution of one or more variables
95
+ * <a href="#heatmap">``.heatmap()``</a>: Plots a heatmap to visualizing a variable across two independent dimensions
96
+ * <a href="#hexbins">``.hexbins()``</a>: Plots hex bins
97
+ * <a href="#histogram">``.hist()``</a>: Plots the distribution of one or histograms as a set of bins
98
+ * <a href="#kde-density">``.kde()``</a>: Plots the kernel density estimate of one or more variables.
99
+ * <a href="#the-plot-method">``.line()``</a>: Plots a line chart (such as for a time series)
100
+ * <a href="#scatter">``.scatter()``</a>: Plots a scatter chart comparing two variables
101
+ * <a href="#step">``.step()``</a>: Plots a step chart akin to a line plot
102
+ * <a href="#tables">``.table()``</a>: Generates a SlickGrid DataTable
103
+ * <a href="#groupby">``.violin()``</a>: Plots a violin plot comparing the distribution of one or more variables using the kernel density estimate
104
+
105
+ #### Area
106
+
107
+ Like most other plot types the ``area`` chart supports the three ways of defining a plot outlined above. An area chart is most useful when plotting multiple variables in a stacked chart. This can be achieve by specifying ``x``, ``y``, and ``by`` columns or using the ``columns`` and ``index``/``use_index`` (equivalent to ``x``) options:
108
+
109
+
110
+ ```python
111
+ crime.hvplot.area(x='Year', y=['Robbery', 'Aggravated assault'])
112
+ ```
113
+
114
+ We can also explicitly set ``stacked`` to False and define an ``alpha`` value to compare the values directly:
115
+
116
+
117
+ ```python
118
+ crime.hvplot.area(x='Year', y=['Aggravated assault', 'Robbery'], stacked=False, alpha=0.4)
119
+ ```
120
+
121
+ Another use for an area plot is to visualize the spread of a value. For instance using the flights dataset we may want to see the spread in mean delay values across carriers. For that purpose we compute the mean delay by day and carrier and then the min/max mean delay for across all carriers. Since the output of ``hvplot`` is just a regular holoviews object, we can use the overlay operator (\*) to place the plots on top of each other.
122
+
123
+
124
+ ```python
125
+ delay_min_max = flights.groupby(['day', 'carrier'])['carrier_delay'].mean().groupby('day').agg({'min': np.min, 'max': np.max})
126
+ delay_mean = flights.groupby('day')['carrier_delay'].mean()
127
+
128
+ delay_min_max.hvplot.area(x='day', y='min', y2='max', alpha=0.2) * delay_mean.hvplot()
129
+ ```
130
+
131
+ #### Bars
132
+
133
+ In the simplest case we can use ``.hvplot.bar`` to plot ``x`` against ``y``. We'll use ``rot=90`` to rotate the tick labels on the x-axis making the years easier to read:
134
+
135
+
136
+ ```python
137
+ crime.hvplot.bar(x='Year', y='Violent Crime rate', rot=90, width=900)
138
+ ```
139
+
140
+ If we want to compare multiple columns instead we can set ``y`` to a list of columns. Using the ``stacked`` option we can then compare the column values more easily:
141
+
142
+
143
+ ```python
144
+ crime.hvplot.bar(x='Year', y=['Violent crime total', 'Property crime total'],
145
+ stacked=True, rot=90, width=900, legend='top_left')
146
+ ```
147
+
148
+ #### Scatter
149
+
150
+ The scatter plot supports many of the same features as the other chart types we have seen so far but can also be colored by another variable using the ``c`` option.
151
+
152
+
153
+ ```python
154
+ crime.hvplot.scatter(x='Violent Crime rate', y='Burglary rate', c='Year')
155
+ ```
156
+
157
+ Anytime that color is being used to represent a dimension, the ``cmap`` option can be used to control the colormap that is used to represent that dimension. Additionally, the colorbar can be disabled using ``colorbar=False``.
158
+
159
+ #### Step
160
+
161
+ A step chart is very similar to a line chart but instead of linearly interpolating between samples the step chart visualizes discrete steps. The point at which to step can be controlled via the ``where`` keyword allowing `'pre'`, `'mid'` (default) and `'post'` values:
162
+
163
+
164
+ ```python
165
+ crime.hvplot.step(x='Year', y=['Robbery', 'Aggravated assault'])
166
+ ```
167
+
168
+ #### HexBins
169
+
170
+ You can create hexagonal bin plots with the ``hexbin`` method. Hexbin plots can be a useful alternative to scatter plots if your data are too dense to plot each point individually. Since these data are not regularly distributed, we'll use the ``logz`` option to map z-axis (color) to a log scale colorbar.
171
+
172
+
173
+ ```python
174
+ # flights.hvplot.hexbin(x='airtime', y='arrdelay', width=600, height=500, logz=True);
175
+ ```
176
+
177
+ <div class="alert alert-warning" role="alert">
178
+ Output suppressed as this is <a href='https://github.com/holoviz/hvplot/pull/653#issuecomment-964056881'>not currently supported</a> with the Matplotlib backend and doesn't display any plot.
179
+ </div>
180
+
181
+ #### Bivariate
182
+
183
+ You can create a 2D density plot with the ``bivariate`` method. Bivariate plots can be a useful alternative to scatter plots if your data are too dense to plot each point individually.
184
+
185
+
186
+ ```python
187
+ crime.hvplot.bivariate(x='Violent Crime rate', y='Burglary rate', width=600, height=500)
188
+ ```
189
+
190
+ #### HeatMap
191
+
192
+ A ``HeatMap`` lets us view the relationship between three variables, so we specify the 'x' and 'y' variables and an additional 'C' variable. Additionally we can define a ``reduce_function`` that computes the values for each bin from the samples that fall into it. Here we plot the 'depdelay' (i.e. departure delay) for each day of the month and carrier in the dataset:
193
+
194
+
195
+ ```python
196
+ flights.compute().hvplot.heatmap(x='day', y='carrier', C='depdelay', reduce_function=np.mean).opts(show_values=False)
197
+ ```
198
+
199
+ #### Tables
200
+
201
+ Unlike all other plot types, a table only supports one signature: either all columns are plotted, or a subset of columns can be selected by defining the ``columns`` explicitly:
202
+
203
+
204
+ ```python
205
+ crime.hvplot.table(columns=['Year', 'Population', 'Violent Crime rate'], width=400)
206
+ ```
207
+
208
+ ### Distributions
209
+
210
+ Plotting distributions differs slightly from other plots since they plot only one variable in the simple case rather than plotting two or more variables against each other. Therefore when plotting these plot types no ``index`` or ``x`` value needs to be supplied. Instead:
211
+
212
+ 1. Declare a single ``y`` variable, e.g. ``source.plot.hist(variable)``, or
213
+ 2. Declare a ``y`` variable and ``by`` variable, e.g. ``source.plot.hist(variable, by='Group')``, or
214
+ 3. Declare columns or plot all columns, e.g. ``source.plot.hist()`` or ``source.plot.hist(columns=['A', 'B', 'C'])``
215
+
216
+ #### Histogram
217
+
218
+ The Histogram is the simplest example of a distribution; often we simply plot the distribution of a single variable, in this case the 'Violent Crime rate'. Additionally we can define a range over which to compute the histogram and the number of bins using the ``bin_range`` and ``bins`` arguments respectively:
219
+
220
+
221
+ ```python
222
+ crime.hvplot.hist(y='Violent Crime rate')
223
+ ```
224
+
225
+ Or we can plot the distribution of multiple columns:
226
+
227
+
228
+ ```python
229
+ columns = ['Violent Crime rate', 'Property crime rate', 'Burglary rate']
230
+ crime.hvplot.hist(y=columns, bins=50, alpha=0.5, legend='top', height=400)
231
+ ```
232
+
233
+ We can also group the data by another variable. Here we'll use ``subplots`` to split each carrier out into its own plot:
234
+
235
+
236
+ ```python
237
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
238
+ # flight_subset.hvplot.hist('depdelay', by='carrier', bins=20, bin_range=(-20, 100), width=300, subplots=True);
239
+ ```
240
+
241
+ <div class="alert alert-warning" role="alert">
242
+ Output suppressed as this is <a href='https://github.com/holoviz/holoviews/issues/5111'>not currently supported</a> with the Matplotlib backend and raises an error. Tip: execute <code>flight_subset.compute()</code> before plotting to avoid the error.
243
+ </div>
244
+
245
+ #### KDE (density)
246
+
247
+ You can also create density plots using ``hvplot.kde()`` or ``hvplot.density()``:
248
+
249
+
250
+ ```python
251
+ crime.hvplot.kde(y='Violent Crime rate')
252
+ ```
253
+
254
+ Comparing the distribution of multiple columns is also possible:
255
+
256
+
257
+ ```python
258
+ columns=['Violent Crime rate', 'Property crime rate', 'Burglary rate']
259
+ crime.hvplot.kde(y=columns, alpha=0.5, value_label='Rate', legend='top_right')
260
+ ```
261
+
262
+ The ``hvplot.kde`` also supports the ``by`` keyword:
263
+
264
+
265
+ ```python
266
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
267
+ flight_subset.hvplot.kde('depdelay', by='carrier', xlim=(-20, 70), width=300, subplots=True)
268
+ ```
269
+
270
+ #### Box-Whisker Plots
271
+
272
+ Just like the other distribution-based plot types, the box-whisker plot supports plotting a single column:
273
+
274
+
275
+ ```python
276
+ crime.hvplot.box(y='Violent Crime rate')
277
+ ```
278
+
279
+ It also supports multiple columns and the same options as seen previously (``legend``, ``invert``, ``value_label``):
280
+
281
+
282
+ ```python
283
+ columns=['Burglary rate', 'Larceny-theft rate', 'Motor vehicle theft rate',
284
+ 'Property crime rate', 'Violent Crime rate']
285
+ crime.hvplot.box(y=columns, group_label='Crime', legend=False, value_label='Rate (per 100k)', invert=True)
286
+ ```
287
+
288
+ Lastly, it also supports using the ``by`` keyword to split the data into multiple subsets:
289
+
290
+
291
+ ```python
292
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
293
+ # flight_subset.hvplot.box('depdelay', by='carrier', ylim=(-10, 70));
294
+ ```
295
+
296
+ <div class="alert alert-warning" role="alert">
297
+ Output suppressed as this is <a href='https://github.com/holoviz/holoviews/issues/5120'>not currently supported</a> with the Matplotlib backend and displays an empty plot.
298
+ </div>
299
+
300
+ ## Composing Plots
301
+
302
+ One of the core strengths of HoloViews is the ease of composing
303
+ different plots. Individual plots can be composed using the ``*`` and
304
+ ``+`` operators, which overlay and compose plots into layouts
305
+ respectively. For more information on composing objects, see the
306
+ HoloViews [User Guide](https://holoviews.org/user_guide/Composing_Elements.html).
307
+
308
+ By using these operators we can combine multiple plots into composite plots. A simple example is overlaying two plot types:
309
+
310
+
311
+ ```python
312
+ crime.hvplot(x='Year', y='Violent Crime rate') * crime.hvplot.scatter(x='Year', y='Violent Crime rate', c='k')
313
+ ```
314
+
315
+ We can also lay out different plots and tables together:
316
+
317
+
318
+ ```python
319
+ (crime.hvplot.bar(x='Year', y='Violent Crime rate', rot=90, width=550) +
320
+ crime.hvplot.table(['Year', 'Population', 'Violent Crime rate'], width=420))
321
+ ```
322
+
323
+ ## Large data
324
+
325
+ The previous examples summarized the fairly large airline dataset using statistical plot types that aggregate the data into a feasible subset for plotting. We can instead aggregate the data directly into the viewable image using [datashader](https://datashader.org), which provides a rendering of the entire set of raw data available (as far as the resolution of the screen allows). Here we plot the 'airtime' against the 'distance':
326
+
327
+
328
+ ```python
329
+ flights.hvplot.scatter(x='distance', y='airtime', datashade=True)
330
+ ```
331
+
332
+ ## Groupby
333
+
334
+ Thanks to the ability of HoloViews to explore a parameter space with a set of widgets we can apply a groupby along a particular column or dimension. For example we can view the distribution of departure delays by carrier grouped by day, allowing the user to choose which day to display:
335
+
336
+
337
+ ```python
338
+ flights.hvplot.violin(y='depdelay', by='carrier', groupby='dayofweek', ylim=(-20, 60), height=500)
339
+ ```
340
+
341
+ This user guide merely provided an overview over the available plot types; to see a detailed description on how to customize plots see the [Customization](Customization.ipynb) user guide.
hvplot_docs/Plotting_with_Plotly.md ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This page demonstrates the use of the **Plotly** plotting backend, the equivalent page demonstrating the Matplotlib backend may be found [here](Plotting_with_Matplotlib.ipynb)
2
+
3
+ As we discovered in the [Introduction](Introduction.ipynb), HoloViews allows plotting a variety of data types. Here we will use the sample data module and load the pandas and dask hvPlot API:
4
+
5
+
6
+ ```python
7
+ import numpy as np
8
+ import hvplot.pandas # noqa
9
+ import hvplot.dask # noqa
10
+ ```
11
+
12
+
13
+ ```python
14
+ hvplot.extension('plotly')
15
+ ```
16
+
17
+ As we learned the hvPlot API closely mirrors the [Pandas plotting API](https://pandas.pydata.org/pandas-docs/stable/visualization.html), but instead of generating static images when used in a notebook, it uses HoloViews to generate either static or dynamically streaming Bokeh plots. Static plots can be used in any context, while streaming plots require a live [Jupyter notebook](https://jupyter.org), a deployed [Bokeh Server app](https://bokeh.pydata.org/en/latest/docs/user_guide/server.html), or a deployed [Panel](https://panel.holoviz.org) app.
18
+
19
+ HoloViews provides an extensive, very rich set of objects along with a powerful set of operations to apply, as you can find out in the [HoloViews User Guide](https://holoviews.org/user_guide/index.html). But here we will focus on the most essential mechanisms needed to make your data visualizable, without having to worry about the mechanics going on behind the scenes.
20
+
21
+ We will be focusing on two different datasets:
22
+
23
+ - A small CSV file of US crime data, broken down by state
24
+ - A larger Parquet-format file of airline flight data
25
+
26
+ The ``hvplot.sample_data`` module makes these datasets Intake data catalogue, which we can load either using pandas:
27
+
28
+
29
+ ```python
30
+ from hvplot.sample_data import us_crime, airline_flights
31
+
32
+ crime = us_crime.read()
33
+ print(type(crime))
34
+ crime.head()
35
+ ```
36
+
37
+ Or using dask as a ``dask.DataFrame``:
38
+
39
+
40
+ ```python
41
+ flights = airline_flights.to_dask().persist()
42
+ print(type(flights))
43
+ flights.head()
44
+ ```
45
+
46
+ ## The plot interface
47
+
48
+ The ``dask.dataframe.DataFrame.hvplot``, ``pandas.DataFrame.hvplot`` and ``intake.DataSource.plot`` interfaces (and Series equivalents) from HvPlot provide a powerful high-level API to generate complex plots. The ``.hvplot`` API can be called directly or used as a namespace to generate specific plot types.
49
+
50
+ ### The plot method
51
+
52
+ The most explicit way to use the plotting API is to specify the names of columns to plot on the ``x``- and ``y``-axis respectively:
53
+
54
+
55
+ ```python
56
+ crime.hvplot.line(x='Year', y='Violent Crime rate')
57
+ ```
58
+
59
+ As you'll see in more detail below, you can choose which kind of plot you want to use for the data:
60
+
61
+
62
+ ```python
63
+ crime.hvplot(x='Year', y='Violent Crime rate', kind='scatter')
64
+ ```
65
+
66
+ To group the data by one or more additional columns, specify an additional ``by`` variable. As an example here we will plot the departure delay ('depdelay') as a function of 'distance', grouping the data by the 'carrier'. There are many available carriers, so we will select only two of them so that the plot is readable:
67
+
68
+
69
+ ```python
70
+ flight_subset = flights[flights.carrier.isin(['OH', 'F9'])]
71
+ flight_subset.hvplot(x='distance', y='depdelay', by='carrier', kind='scatter', alpha=0.2, persist=True)
72
+ ```
73
+
74
+ Here we have specified the `x` axis explicitly, which can be omitted if the Pandas index column is already the desired x axis. Similarly, here we specified the `y` axis; by default all of the non-index columns would be plotted (which would be a lot of data in this case). If you don't specify the 'y' axis, it will have a default label named 'value', but you can then provide a y axis label explicitly using the ``value_label`` option.
75
+
76
+ Putting all of this together we will plot violent crime, robbery, and burglary rates on the y-axis, specifying 'Year' as the x, and relabel the y-axis to display the 'Rate'.
77
+
78
+
79
+ ```python
80
+ crime.hvplot(x='Year', y=['Violent Crime rate', 'Robbery rate', 'Burglary rate'],
81
+ value_label='Rate (per 100k people)')
82
+ ```
83
+
84
+ ### The hvplot namespace
85
+
86
+ Instead of using the ``kind`` argument to the plot call, we can use the ``hvplot`` namespace, which lets us easily discover the range of plot types that are supported. Use tab completion to explore the available plot types:
87
+
88
+ ```python
89
+ crime.hvplot.<TAB>
90
+ ```
91
+
92
+ Plot types available include:
93
+
94
+ * <a href="#area">``.area()``</a>: Plots a area chart similar to a line chart except for filling the area under the curve and optionally stacking
95
+ * <a href="#bars">``.bar()``</a>: Plots a bar chart that can be stacked or grouped
96
+ * <a href="#bivariate">``.bivariate()``</a>: Plots 2D density of a set of points
97
+ * <a href="#box-whisker-plots">``.box()``</a>: Plots a box-whisker chart comparing the distribution of one or more variables
98
+ * <a href="#heatmap">``.heatmap()``</a>: Plots a heatmap to visualizing a variable across two independent dimensions
99
+ * <a href="#hexbins">``.hexbins()``</a>: Plots hex bins
100
+ * <a href="#histogram">``.hist()``</a>: Plots the distribution of one or histograms as a set of bins
101
+ * <a href="#kde-density">``.kde()``</a>: Plots the kernel density estimate of one or more variables.
102
+ * <a href="#the-plot-method">``.line()``</a>: Plots a line chart (such as for a time series)
103
+ * <a href="#scatter">``.scatter()``</a>: Plots a scatter chart comparing two variables
104
+ * <a href="#step">``.step()``</a>: Plots a step chart akin to a line plot
105
+ * <a href="#tables">``.table()``</a>: Generates a SlickGrid DataTable
106
+ * <a href="#groupby">``.violin()``</a>: Plots a violin plot comparing the distribution of one or more variables using the kernel density estimate
107
+
108
+ #### Area
109
+
110
+ Like most other plot types the ``area`` chart supports the three ways of defining a plot outlined above. An area chart is most useful when plotting multiple variables in a stacked chart. This can be achieve by specifying ``x``, ``y``, and ``by`` columns or using the ``columns`` and ``index``/``use_index`` (equivalent to ``x``) options:
111
+
112
+
113
+ ```python
114
+ crime.hvplot.area(x='Year', y=['Robbery', 'Aggravated assault'])
115
+ ```
116
+
117
+ We can also explicitly set ``stacked`` to False and define an ``alpha`` value to compare the values directly:
118
+
119
+
120
+ ```python
121
+ crime.hvplot.area(x='Year', y=['Aggravated assault', 'Robbery'], stacked=False, alpha=0.4)
122
+ ```
123
+
124
+ Another use for an area plot is to visualize the spread of a value. For instance using the flights dataset we may want to see the spread in mean delay values across carriers. For that purpose we compute the mean delay by day and carrier and then the min/max mean delay for across all carriers. Since the output of ``hvplot`` is just a regular holoviews object, we can use the overlay operator (\*) to place the plots on top of each other.
125
+
126
+
127
+ ```python
128
+ delay_min_max = flights.groupby(['day', 'carrier'])['carrier_delay'].mean().groupby('day').agg({'min': np.min, 'max': np.max})
129
+ delay_mean = flights.groupby('day')['carrier_delay'].mean()
130
+
131
+ delay_min_max.hvplot.area(x='day', y='min', y2='max', alpha=0.2) * delay_mean.hvplot()
132
+ ```
133
+
134
+ #### Bars
135
+
136
+ In the simplest case we can use ``.hvplot.bar`` to plot ``x`` against ``y``. We'll use ``rot=90`` to rotate the tick labels on the x-axis making the years easier to read:
137
+
138
+
139
+ ```python
140
+ crime.hvplot.bar(x='Year', y='Violent Crime rate', rot=90)
141
+ ```
142
+
143
+ If we want to compare multiple columns instead we can set ``y`` to a list of columns. Using the ``stacked`` option we can then compare the column values more easily:
144
+
145
+
146
+ ```python
147
+ crime.hvplot.bar(x='Year', y=['Violent crime total', 'Property crime total'],
148
+ stacked=True, rot=90, width=800, legend='top_left')
149
+ ```
150
+
151
+ #### Scatter
152
+
153
+ The scatter plot supports many of the same features as the other chart types we have seen so far but can also be colored by another variable using the ``c`` option.
154
+
155
+
156
+ ```python
157
+ crime.hvplot.scatter(x='Violent Crime rate', y='Burglary rate', c='Year')
158
+ ```
159
+
160
+ Anytime that color is being used to represent a dimension, the ``cmap`` option can be used to control the colormap that is used to represent that dimension. Additionally, the colorbar can be disabled using ``colorbar=False``.
161
+
162
+ #### Step
163
+
164
+ A step chart is very similar to a line chart but instead of linearly interpolating between samples the step chart visualizes discrete steps. The point at which to step can be controlled via the ``where`` keyword allowing `'pre'`, `'mid'` (default) and `'post'` values:
165
+
166
+
167
+ ```python
168
+ crime.hvplot.step(x='Year', y=['Robbery', 'Aggravated assault'])
169
+ ```
170
+
171
+ #### HexBins
172
+
173
+ You can create hexagonal bin plots with the ``hexbin`` method. Hexbin plots can be a useful alternative to scatter plots if your data are too dense to plot each point individually. Since these data are not regularly distributed, we'll use the ``logz`` option to map z-axis (color) to a log scale colorbar.
174
+
175
+
176
+ ```python
177
+ # flights.hvplot.hexbin(x='airtime', y='arrdelay', width=600, height=500, logz=True)
178
+ ```
179
+
180
+ <div class="alert alert-warning" role="alert">
181
+ HexBins plots <a href='https://github.com/holoviz/holoviews/issues/5219'>not yet supported</a> with the Plotly backend.
182
+ </div>
183
+
184
+ #### Bivariate
185
+
186
+ You can create a 2D density plot with the ``bivariate`` method. Bivariate plots can be a useful alternative to scatter plots if your data are too dense to plot each point individually.
187
+
188
+
189
+ ```python
190
+ # crime.hvplot.bivariate(x='Violent Crime rate', y='Burglary rate', width=600, height=500)
191
+ ```
192
+
193
+ <div class="alert alert-warning" role="alert">
194
+ Commented as this is <a href='https://github.com/holoviz/holoviews/issues/5220'>not currently supported</a> with the Plotly backend and raises an error.
195
+ </div>
196
+
197
+ #### HeatMap
198
+
199
+ A ``HeatMap`` lets us view the relationship between three variables, so we specify the 'x' and 'y' variables and an additional 'C' variable. Additionally we can define a ``reduce_function`` that computes the values for each bin from the samples that fall into it. Here we plot the 'depdelay' (i.e. departure delay) for each day of the month and carrier in the dataset:
200
+
201
+
202
+ ```python
203
+ flights.compute().hvplot.heatmap(x='day', y='carrier', C='depdelay', reduce_function=np.mean, colorbar=True)
204
+ ```
205
+
206
+ #### Tables
207
+
208
+ Unlike all other plot types, a table only supports one signature: either all columns are plotted, or a subset of columns can be selected by defining the ``columns`` explicitly:
209
+
210
+
211
+ ```python
212
+ crime.hvplot.table(columns=['Year', 'Population', 'Violent Crime rate'], width=400)
213
+ ```
214
+
215
+ ### Distributions
216
+
217
+ Plotting distributions differs slightly from other plots since they plot only one variable in the simple case rather than plotting two or more variables against each other. Therefore when plotting these plot types no ``index`` or ``x`` value needs to be supplied. Instead:
218
+
219
+ 1. Declare a single ``y`` variable, e.g. ``source.plot.hist(variable)``, or
220
+ 2. Declare a ``y`` variable and ``by`` variable, e.g. ``source.plot.hist(variable, by='Group')``, or
221
+ 3. Declare columns or plot all columns, e.g. ``source.plot.hist()`` or ``source.plot.hist(columns=['A', 'B', 'C'])``
222
+
223
+ #### Histogram
224
+
225
+ The Histogram is the simplest example of a distribution; often we simply plot the distribution of a single variable, in this case the 'Violent Crime rate'. Additionally we can define a range over which to compute the histogram and the number of bins using the ``bin_range`` and ``bins`` arguments respectively:
226
+
227
+
228
+ ```python
229
+ crime.hvplot.hist(y='Violent Crime rate')
230
+ ```
231
+
232
+ Or we can plot the distribution of multiple columns:
233
+
234
+
235
+ ```python
236
+ columns = ['Violent Crime rate', 'Property crime rate', 'Burglary rate']
237
+ crime.hvplot.hist(y=columns, bins=50, alpha=0.5, legend='top', height=400)
238
+ ```
239
+
240
+ We can also group the data by another variable. Here we'll use ``subplots`` to split each carrier out into its own plot:
241
+
242
+
243
+ ```python
244
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
245
+ flight_subset.hvplot.hist('depdelay', by='carrier', bins=20, bin_range=(-20, 100), width=300, subplots=True)
246
+ ```
247
+
248
+ #### KDE (density)
249
+
250
+ You can also create density plots using ``hvplot.kde()`` or ``hvplot.density()``:
251
+
252
+
253
+ ```python
254
+ crime.hvplot.kde(y='Violent Crime rate')
255
+ ```
256
+
257
+ Comparing the distribution of multiple columns is also possible:
258
+
259
+
260
+ ```python
261
+ columns=['Violent Crime rate', 'Property crime rate', 'Burglary rate']
262
+ crime.hvplot.kde(y=columns, alpha=0.5, value_label='Rate', legend='top_right')
263
+ ```
264
+
265
+ The ``hvplot.kde`` also supports the ``by`` keyword:
266
+
267
+
268
+ ```python
269
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
270
+ flight_subset.hvplot.kde('depdelay', by='carrier', xlim=(-20, 70), width=300, subplots=True)
271
+ ```
272
+
273
+ #### Box-Whisker Plots
274
+
275
+ Just like the other distribution-based plot types, the box-whisker plot supports plotting a single column:
276
+
277
+
278
+ ```python
279
+ crime.hvplot.box(y='Violent Crime rate')
280
+ ```
281
+
282
+ It also supports multiple columns and the same options as seen previously (``legend``, ``invert``, ``value_label``):
283
+
284
+
285
+ ```python
286
+ columns=['Burglary rate', 'Larceny-theft rate', 'Motor vehicle theft rate',
287
+ 'Property crime rate', 'Violent Crime rate']
288
+ crime.hvplot.box(y=columns, group_label='Crime', legend=False, value_label='Rate (per 100k)', invert=True)
289
+ ```
290
+
291
+ Lastly, it also supports using the ``by`` keyword to split the data into multiple subsets:
292
+
293
+
294
+ ```python
295
+ flight_subset = flights[flights.carrier.isin(['AA', 'US', 'OH'])]
296
+ flight_subset.hvplot.box('depdelay', by='carrier', ylim=(-10, 70))
297
+ ```
298
+
299
+ ## Composing Plots
300
+
301
+ One of the core strengths of HoloViews is the ease of composing
302
+ different plots. Individual plots can be composed using the ``*`` and
303
+ ``+`` operators, which overlay and compose plots into layouts
304
+ respectively. For more information on composing objects, see the
305
+ HoloViews [User Guide](https://holoviews.org/user_guide/Composing_Elements.html).
306
+
307
+ By using these operators we can combine multiple plots into composite plots. A simple example is overlaying two plot types:
308
+
309
+
310
+ ```python
311
+ crime.hvplot(x='Year', y='Violent Crime rate') * crime.hvplot.scatter(x='Year', y='Violent Crime rate', c='k')
312
+ ```
313
+
314
+ We can also lay out different plots and tables together:
315
+
316
+
317
+ ```python
318
+ (crime.hvplot.bar(x='Year', y='Violent Crime rate', rot=90, width=550) +
319
+ crime.hvplot.table(['Year', 'Population', 'Violent Crime rate'], width=420))
320
+ ```
321
+
322
+ ## Large data
323
+
324
+ The previous examples summarized the fairly large airline dataset using statistical plot types that aggregate the data into a feasible subset for plotting. We can instead aggregate the data directly into the viewable image using [datashader](https://datashader.org), which provides a rendering of the entire set of raw data available (as far as the resolution of the screen allows). Here we plot the 'airtime' against the 'distance':
325
+
326
+
327
+ ```python
328
+ flights.hvplot.scatter(x='distance', y='airtime', datashade=True)
329
+ ```
330
+
331
+ ## Groupby
332
+
333
+ Thanks to the ability of HoloViews to explore a parameter space with a set of widgets we can apply a groupby along a particular column or dimension. For example we can view the distribution of departure delays by carrier grouped by day, allowing the user to choose which day to display:
334
+
335
+
336
+ ```python
337
+ flights.hvplot.violin(y='depdelay', by='carrier', groupby='dayofweek', ylim=(-20, 60), height=500)
338
+ ```
339
+
340
+ This user guide merely provided an overview over the available plot types; to see a detailed description on how to customize plots see the [Customization](Customization.ipynb) user guide.
hvplot_docs/Statistical_Plots.md ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ In addition to the plots available via the plot interface, hvPlot makes a number of more sophisticated, statistical plots available that are modelled on ``pandas.plotting``. To explore these, we will load the iris and stocks datasets from Bokeh:
2
+
3
+
4
+ ```python
5
+ import pandas as pd
6
+ import hvplot.pandas # noqa
7
+
8
+ from bokeh.sampledata import iris, stocks
9
+
10
+ iris = iris.flowers
11
+ ```
12
+
13
+ ### Scatter Matrix
14
+
15
+ When working with multi-dimensional data, it is often difficult to understand the relationship between all the different variables. A ``scatter_matrix`` makes it possible to visualize all of the pairwise relationships in a compact format. ``hvplot.scatter_matrix`` is closely modelled on ``pandas.plotting.scatter_matrix``:
16
+
17
+
18
+ ```python
19
+ hvplot.scatter_matrix(iris, c="species")
20
+ ```
21
+
22
+ Compared to a static Seaborn/Matplotlib-based plot, here it is easy to explore the data interactively thanks to Bokeh's linked zooming, linked panning, and linked brushing (using the ``box_select`` and ``lasso_select`` tools).
23
+
24
+ ### Parallel Coordinates
25
+
26
+ Parallel coordinate plots provide another way of visualizing multi-variate data. ``hvplot.parallel_coordinates`` provides a simple API to create such a plot, modelled on the API of `pandas.plotting.parallel_coordinates()`:
27
+
28
+
29
+ ```python
30
+ hvplot.parallel_coordinates(iris, "species")
31
+ ```
32
+
33
+ The plot quickly clarifies the relationship between different variables, highlighting the difference of the "setosa" species in the petal width and length dimensions.
34
+
35
+ ### Andrews Curves
36
+
37
+
38
+ Another similar approach is to visualize the dimensions using Andrews curves, which are constructed by generating a Fourier series from the features of each observation, visualizing the aggregate differences between classes. The ``hvplot.andrews_curves()`` function provides a simple API to generate Andrews curves from a datafrom, closely matching the API of ``pandas.plotting.andrews_curves()``:
39
+
40
+
41
+ ```python
42
+ hvplot.andrews_curves(iris, "species")
43
+ ```
44
+
45
+ Once again we can see the significant difference of the setosa species. However, unlike the parallel coordinate plot, the Andrews plot does not give any real quantitative insight into the features that drive those differences.
46
+
47
+ ### Lag Plot
48
+
49
+ Lastly, for the analysis of time series hvplot offers a so called lag plot, implemented by the ``hvplot.lag_plot()`` function, modelled on the matching ``pandas.plotting.lag_plot()`` function.
50
+
51
+ As an example we will compare the closing stock prices of Apple and IBM from 2000-2013 using a lag of 365 days:
52
+
53
+
54
+ ```python
55
+ index = pd.DatetimeIndex(stocks.AAPL['date'])
56
+ stock_df = pd.DataFrame({'IBM': stocks.IBM['close'], 'AAPL': stocks.AAPL['close']}, index=index)
57
+
58
+ hvplot.lag_plot(stock_df, lag=365, alpha=0.3)
59
+ ```
60
+
61
+ Using this plot it becomes apparent that Apple was significantly more volatile over the analyzed time scale. In other words, its price at a particular point in time sometimes differed significantly from the price 365 days in the past. This also becomes visible in a simple line chart of the same data:
62
+
63
+
64
+ ```python
65
+ stock_df.hvplot.line()
66
+ ```
67
+
68
+ These plot types can help you make sense of complex datasets. See [holoviews.org](https://holoviews.org) for many other plots and tools that can be used alongside those from hvPlot for other purposes.
hvplot_docs/Streaming.md ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ hvPlot supports [streamz](https://github.com/mrocklin/streamz) DataFrame and Series objects, automatically generating streaming plots in a Jupyter notebook or deployed as a [Bokeh Server app](https://bokeh.pydata.org/en/latest/docs/user_guide/server.html).
2
+
3
+ All hvPlot methods on streamz objects return HoloViews `DynamicMap` objects that update the plot whenever `streamz` triggers an event. For more information on `DynamicMap` and HoloViews dynamic plotting support, see the [HoloViews User Guide](https://holoviews.org/user_guide); here we will focus on using the simple, high-level hvPlot API rather than on the details of how events and data flow behind the scenes.
4
+
5
+ **All plots generated by the streamz plotting interface dynamically stream data from Python into the web browser. The web version for this page includes *screen captures* of the streaming visualizations, not live streaming data.**
6
+
7
+ As for any of the data backends, we start by patching the streamz library with the plotting API:
8
+
9
+
10
+ ```python
11
+ import hvplot.streamz # noqa
12
+ ```
13
+
14
+ ## Basic plotting
15
+
16
+ Throughout this section we will be using the ``Random`` object from streamz, which provides an easy way of generating a DataFrame of random streaming data but which could be substituted with any streamz ``DataFrame`` or ``Series`` driven by a live, external data source instead. To stop the ``Random`` stream you can call ``df.stop()`` at any point.
17
+
18
+
19
+ ```python
20
+ from streamz.dataframe import Random
21
+ df = Random(interval='200ms', freq='50ms')
22
+ ```
23
+
24
+ <img src='https://assets.holoviews.org/hvplot/gifs/df_streamz.gif' width=400px></img>
25
+
26
+ The plot method on Series and DataFrame is a simple wrapper around a
27
+ line plot, which will plot all columns:
28
+
29
+
30
+ ```python
31
+ df.hvplot()
32
+ ```
33
+
34
+ <center><img src='https://assets.holoviews.org/hvplot/gifs/df_plot.gif' width=700px></img></center>
35
+
36
+ The plot method can also be called on a Series, plotting a specific column:
37
+
38
+
39
+ ```python
40
+ df.z.cumsum().hvplot()
41
+ ```
42
+
43
+ <center><img src='https://assets.holoviews.org/hvplot/gifs/df_curve.gif' width=700px></img></center>
44
+
45
+ Actually, *all* the functionality described in the [Plotting](Plotting.ipynb) user guide should work just the same as it does for a regular (non-streaming) dask or pandas DataFrame or Series, but will now update as new data appears.
46
+
47
+ ## Controlling the backlog
48
+
49
+ The main difference when using a streaming DataFrame is that a only a certain amount of data will be buffered and displayed. The number of rows of data that will be buffered can be controlled by the ``backlog`` parameter. Here, let's buffer and display 10 rows of our streaming DataFrame as a table:
50
+
51
+
52
+ ```python
53
+ df.hvplot.table(width=400, backlog=10)
54
+ ```
55
+
56
+ <center><img src='https://assets.holoviews.org/hvplot/gifs/df_table.gif' width=400px></img></center>
57
+
58
+ This of course only has an effect when we are directly streaming data point by point. When we have an aggregate DataFrame, the plot will continuously accumulate updates:
59
+
60
+
61
+ ```python
62
+ df.groupby('y').sum().hvplot.bar(x='y')
63
+ ```
64
+
65
+ <center><img src='https://assets.holoviews.org/hvplot/gifs/df_bars.gif' width=700px></img></center>
66
+
67
+ ## Composing Plots
68
+
69
+ hvPlot is a convenient API for generating HoloViews objects. One of the core strengths of HoloViews objects is the ease with which they can be composed, which works with streaming plots just as with static ones. Individual plots can be composed using the ``*`` and ``+`` operators, which overlay and compose plots into layouts respectively. For more information on composing objects see the HoloViews [User Guide](https://holoviews.org/user_guide/Composing_Elements.html).
70
+
71
+ By using these operators we can combine multiple plots into composite Overlay and Layout objects, and lay them out in two columns using the ``Layout.cols`` method:
72
+
73
+
74
+ ```python
75
+ (df.hvplot.line(width=400, backlog=100) * df.hvplot.scatter(width=400, backlog=100) +
76
+ df.groupby('y').sum().hvplot.bar('y', 'x', width=400) +
77
+ df.hvplot.box(width=400) + df.x.hvplot.kde(width=400, shared_axes=False)).cols(2)
78
+ ```
79
+
80
+ <center><img src='https://assets.holoviews.org/hvplot/gifs/df_composite.gif' width=700px></img></center>
81
+
82
+ ## Deployment as Bokeh apps
83
+
84
+ HoloViews objects automatically render themselves in Jupyter notebook cells, but when deploying a bokeh app the plot has to be rendered explicitly. Deploying as a Bokeh Server app allows you to share live, dynamically updated visualizations like those for streaming data, backed by a running Python process.
85
+
86
+ The following example describes how to set up a streaming DataFrame, declare some plots, compose them, set up a callback to update the plot and finally convert the composite plot to a bokeh Document, which can be served from a script using ``bokeh serve`` on the commandline.
87
+
88
+ ```python
89
+
90
+ import numpy as np
91
+ import pandas as pd
92
+ import hvplot.streamz
93
+ import holoviews as hv
94
+
95
+ from streamz import Stream
96
+ from streamz.dataframe import DataFrame
97
+
98
+ renderer = hv.renderer('bokeh')
99
+
100
+ # Set up streaming DataFrame
101
+ stream = Stream()
102
+ index = pd.DatetimeIndex([])
103
+ example = pd.DataFrame({'x': [], 'y': [], 'z': []},
104
+ columns=['x', 'y', 'z'], index=[])
105
+ df = DataFrame(stream, example=example)
106
+ cumulative = df.cumsum()[['x', 'z']]
107
+
108
+ # Declare plots
109
+ line = cumulative.hvplot.line(width=400)
110
+ scatter = cumulative.hvplot.scatter(width=400)
111
+ bars = df.groupby('y').sum().hvplot.bar(width=400)
112
+ box = df.hvplot.box(width=400)
113
+ kde = df.x.hvplot.kde(width=400)
114
+
115
+ # Compose plots
116
+ layout = (line * scatter + bars + box + kde).cols(2)
117
+
118
+ # Set up callback with streaming data
119
+ def emit():
120
+ now = pd.datetime.now()
121
+ delta = np.timedelta64(500, 'ms')
122
+ index = pd.date_range(np.datetime64(now)-delta, now, freq='100ms')
123
+ df = pd.DataFrame({'x': np.random.randn(len(index)),
124
+ 'y': np.random.randint(0, 10, len(index)),
125
+ 'z': np.random.randn(len(index))},
126
+ columns=['x', 'y', 'z'], index=index)
127
+ stream.emit(df)
128
+
129
+ # Render layout to bokeh server Document and attach callback
130
+ doc = renderer.server_doc(layout)
131
+ doc.title = 'Streamz HoloViews based Plotting API Bokeh App Demo'
132
+ doc.add_periodic_callback(emit, 500)
133
+ ```
134
+
135
+ For more details on deploying Bokeh apps see the HoloViews [User Guide](https://holoviews.org/user_guide/Deploying_Bokeh_Apps.html).
136
+
137
+ ## Using HoloViews directly
138
+
139
+ HoloViews itself includes first class support for streamz DataFrame and Series; for more details see the [Streaming Data section](https://holoviews.org/user_guide/Streaming_Data.html) in the HoloViews documentation.
hvplot_docs/Subplots.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Subplots
2
+
3
+ When plotting multiple columns, hvPlot will overlay the plots onto one axis by default so that they can be compared easily in a compact format:
4
+
5
+
6
+ ```python
7
+ import xarray as xr
8
+ import hvplot.pandas # noqa
9
+ import hvplot.xarray # noqa
10
+
11
+ from hvplot.sample_data import airline_flights, us_crime
12
+
13
+ us_crime.hvplot(x='Year', y=['Burglary rate', 'Violent Crime rate', 'Robbery rate'], value_label='Rate')
14
+ ```
15
+
16
+ If you wish, you can instead set `subplots=True` to split each column into its own separate plot:
17
+
18
+
19
+ ```python
20
+ us_crime.hvplot(x='Year', y=['Burglary rate', 'Violent Crime rate', 'Robbery rate'],
21
+ value_label='Rate', subplots=True, width=300, height=200)
22
+ ```
23
+
24
+ By default, the subplots will have linked, normalized axes, to facilitate comparing the numerical values across plots (try panning or zooming in any one of the plots, to see how the others change.)
25
+
26
+ However, if the data covers widely different numerical ranges, you can specify `shared_axes=False` to give each plot its own range:
27
+
28
+
29
+ ```python
30
+ us_crime.hvplot(x='Year', y=['Robbery', 'Robbery rate', 'Burglary', 'Burglary rate'],
31
+ width=350, height=300, subplots=True, shared_axes=False).cols(2)
32
+ ```
33
+
34
+ (Notice the very different y axis ranges between the plots.) Here we also specified `.cols(2)` to allow up to two plots per line, wrapping the rest onto subsequent rows.
35
+
36
+ You can use the `subplots=True` (and `shared_axes` if desired) arguments when using the ``by`` keyword as well, if you want to group the data along a dimension:
37
+
38
+
39
+ ```python
40
+ flights = airline_flights.read()
41
+
42
+ flight_subset = flights[flights.carrier.isin(['OH', 'F9', 'US'])].sample(2000)
43
+
44
+ flight_subset.hvplot.scatter(x='arrdelay', y='depdelay', by='carrier',
45
+ subplots=True, width=250, height=250, alpha=0.1)
46
+ ```
47
+
48
+ ## Grids
49
+
50
+
51
+ `subplot=True` lays out plots sequentially, breaking lines if requested with the `.cols()` method but otherwise formatting each plot independently. You can instead arrange multidimensional data into an explicit 1D row of plots or a 2D grid of plots with shared axes, to allow easier comparisons across large numbers of plots. To make a row or grid plot, just specify the ``col`` keyword and add a ``row`` keyword if you want a grid:
52
+
53
+
54
+ ```python
55
+ flight_subset.sort_values('dayofweek').hvplot.scatter(x='arrdelay', y='depdelay',
56
+ row='dayofweek', col='carrier', alpha=0.2)
57
+ ```
58
+
59
+ (Just declaring `row` to get a single column is not currently supported.) Here you can see that compared to the subplot versions above, the axis ticks and labels are shared to save space and make comparisons easier.
60
+
61
+ If you do not require an x axis and y axis for each plot at all, you can disable it with the ``xaxis`` and ``yaxis`` options:
62
+
63
+
64
+ ```python
65
+ air_ds = xr.tutorial.open_dataset('air_temperature').load()
66
+
67
+ air_ds.air.isel(time=slice(0, 5)).hvplot(col='time', xaxis=False, yaxis=False, colorbar=False)
68
+ ```
69
+
70
+ Using subplots and grids in this way is supported throughout the hvPlot API, making it simple to determine how you want your data laid out and overlaid.
hvplot_docs/Timeseries_Data.md ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Almost all the other sections in the user guide mention timeseries. This section demonstrates the special functionality that hvPlot provides specifically for dealing with time.
2
+
3
+
4
+ ```python
5
+ import hvplot.pandas # noqa
6
+ from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature as sst
7
+
8
+ sst.hvplot()
9
+ ```
10
+
11
+ By default, the index will be used as the x-axis when plotting tabular data. If the index is composed of datetimes, and they are not in chronological order, hvPlot will try to sort them before plotting (unless you set ``sort_date=False``).
12
+
13
+
14
+ ```python
15
+ scrampled = sst.sample(frac=1)
16
+ scrampled.hvplot()
17
+ ```
18
+
19
+ ### Tickers
20
+
21
+ The datetime tickers will be set to a default that is meant to fit in the allotted space. If you'd rather use a different format, then you can declare an explicit date ticker according to the rules on [Bokeh DatetimeTickFormatter](https://bokeh.pydata.org/en/latest/docs/reference/models/formatters.html#bokeh.models.formatters.DatetimeTickFormatter).
22
+
23
+
24
+ ```python
25
+ from bokeh.models.formatters import DatetimeTickFormatter
26
+
27
+ formatter = DatetimeTickFormatter(months='%b %Y')
28
+ sst.hvplot(xformatter=formatter)
29
+ ```
30
+
31
+ ### Auto-range
32
+
33
+ *(Available with HoloViews >= 1.16)*
34
+
35
+ Automatic ranging, aka auto-ranging, on the data in x or y is supported, making it easy to scale the given axes and fit the entire visible curve after a zoom or pan. Try zooming in on the plot and panning around, the y range nicely adapt to fit the curve.
36
+
37
+
38
+ ```python
39
+ sst.hvplot(autorange="y")
40
+ ```
41
+
42
+ ### Pandas datetime features
43
+
44
+ hvPlot takes advantage of datetime features to make it trivial to produce plots that are aggregated on some feature of the date. For instance in the case of temperature data, it might be interesting to examine the monthly temperature distribution. We can easily do that by setting ``by='index.month'``.
45
+
46
+
47
+ ```python
48
+ sst.hvplot.violin(by='index.month')
49
+ ```
50
+
51
+ We can also use these datetime features as the ``x`` and ``y``. Here we'll look at the mean temperature at each hour of the day for each month in our dataset.
52
+
53
+
54
+ ```python
55
+ sst.hvplot.heatmap(x='index.hour', y='index.month', C='temperature', cmap='reds')
56
+ ```
57
+
58
+ Combining this with the information from the [section on widgets](Widgets.ipynb), we can even use the datetime features to produce a plot that steps through each month in the data.
59
+
60
+
61
+ ```python
62
+ sst.hvplot(groupby=['index.year', 'index.month'], widget_type='scrubber', widget_location='bottom')
63
+ ```
64
+
65
+ ### Xarray datetime features
66
+
67
+ The same datetime features can be used with xarray data as well, although for now, the functionality is only supported for non-gridded output.
68
+
69
+
70
+ ```python
71
+ import xarray as xr
72
+ import hvplot.xarray # noqa
73
+
74
+ air_ds = xr.tutorial.open_dataset('air_temperature').load()
75
+ air_ds
76
+ ```
77
+
78
+ Similar to how we did for sea surface temperature above, we can get the distribution of air temperature by month.
79
+
80
+
81
+ ```python
82
+ air_ds.hvplot.violin(y='air', by='time.month')
83
+ ```
84
+
85
+ Once we reduce the dimensionality (by taking the mean over 'lat' and 'lon'), we can groupby various datetime features.
86
+
87
+
88
+ ```python
89
+ air_ds.mean(dim=['lat', 'lon']).hvplot(by='time.hour', groupby=['time.year', 'time.month'])
90
+ ```
91
+
92
+ Note that xarray supports grouping and aggregation using a similar syntax. To learn more about timeseries in xarray, see the [xarray timeseries docs](https://xarray.pydata.org/en/stable/time-series.html).
93
+
94
+ ### Downsample time series
95
+
96
+ *(Available with HoloViews >= 1.16)*
97
+
98
+ An option when working with large time series is to downsample the data before plotting it. This can be done with `downsample=True`, which applies the `lttb` (Largest Triangle Three Buckets) algorithm to the data.
99
+
100
+
101
+ ```python
102
+ sst.hvplot(label="original") * sst.hvplot(downsample=True, label="downsampled")
103
+ ```
hvplot_docs/Viewing.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ hvPlot is written to work well inside a Jupyter notebook, from the interactive Python command prompt, or inside a Python batch script. In this user guide we will discover how to use hvPlot to view plots in each of these cases and how to save the plots to a separate file.
2
+
3
+ ## Using hvPlot in different contexts
4
+
5
+ ### Notebook
6
+
7
+ In a Jupyter notebook, hvPlot will return HoloViews objects that display themselves, as long as the plotting library has been loaded. The easiest way of loading the plotting library is to import one of the plotting accessors such as pandas' one with `hvplot.pandas`:
8
+
9
+
10
+ ```python
11
+ import hvplot.pandas # noqa
12
+ ```
13
+
14
+ The above import statement triggers the loading of the default plotting library, i.e. Bokeh.
15
+
16
+ Here we will load some of the sample data and then compose the HoloViews objects into a layout:
17
+
18
+
19
+ ```python
20
+ from hvplot.sample_data import airline_flights, us_crime
21
+
22
+ violent_crime = us_crime.hvplot(x='Year', y='Violent Crime rate', width=400)
23
+ burglaries = us_crime.hvplot(x='Year', y='Burglary rate', width=400)
24
+
25
+ violent_crime + burglaries
26
+ ```
27
+
28
+ ### Using Panel
29
+
30
+ To display the object from inside a function it's recommended that you use [Panel](https://panel.holoviz.org). Panel can also be used to create more complicated layouts, to add additional interactivity, and to deploy servers.
31
+
32
+
33
+ ```python
34
+ import panel as pn
35
+
36
+ pane = pn.panel(violent_crime)
37
+ pane
38
+ ```
39
+
40
+ This ``pane`` can be updated with another HoloViews object replacing the plot:
41
+
42
+
43
+ ```python
44
+ pane.object = burglaries
45
+ ```
46
+
47
+ Or the object can be updated inplace:
48
+
49
+
50
+ ```python
51
+ pane.object *= violent_crime
52
+ ```
53
+
54
+ ### Python Command Prompt & Scripts
55
+
56
+ When working outside the notebook we can instead use the ``hvplot.show`` function, which will open the plot in a new browser window:
57
+
58
+ <img src="../assets/console.png" style="display: table; margin: 0 auto;"></img>
59
+
60
+ For static plots this will simply save a temporary file and open it, however for dynamic and [datashaded](https://datashader.org) plots it will automatically launch a Bokeh server, enabling all the dynamic features.
61
+
62
+ <img src="../assets/console_server.gif" style="display: table; margin: 0 auto;"></img>
63
+
64
+ ## Saving plots
65
+
66
+ When looking at any Bokeh plot in a web browser, you can use the toolbar's "Save" tool to export the plot as a PNG (try it on one of the plots above!).
67
+
68
+ hvPlot also provides a convenient ``save`` function to export HoloViews objects to a file. By default it will save the plot as HTML:
69
+
70
+
71
+ ```python
72
+ plot = airline_flights.hvplot.hexbin(x='airtime', y='arrdelay', colorbar=True, width=600, height=500, logz=True)
73
+
74
+ hvplot.save(plot, 'test.html')
75
+ ```
76
+
77
+ By default, the HTML file generated will depend on loading JavaScript code for BokehJS from the online CDN repository, to reduce the file size. If you need to work in an airgapped or no-network environment, you can declare that `INLINE` resources should be used instead of `CDN`:
78
+
79
+
80
+ ```python
81
+ from bokeh.resources import INLINE
82
+ hvplot.save(plot, 'test.html', resources=INLINE)
83
+ ```
84
+
85
+ Finally, if a 'png' file extension is specified, the exported plot will be rendered as a PNG, which currently requires Selenium and PhantomJS to be installed:
86
+
87
+
88
+ ```python
89
+ hvplot.save(plot, 'test.png')
90
+ ```
hvplot_docs/Widgets.md ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ As we have seen in previous sections, hvPlot bakes in interactivity by automatically creating widgets when using ``groupby``. These widgets can be refined using [Panel](https://panel.holoviz.org). Panel allows you to customize the interactivity of your hvPlot output and provides more fine-grained control over the layout.
2
+
3
+ <div class="alert alert-warning" role="alert">
4
+ When viewing on a static website, the widgets will be inoperable. To explore this functionality fully, download the notebook and run it!
5
+ </div>
6
+
7
+
8
+ ```python
9
+ import panel as pn
10
+ import hvplot.pandas # noqa
11
+
12
+ from bokeh.sampledata.iris import flowers
13
+
14
+ ```
15
+
16
+ When ``groupby`` is used, the default widget is selected for the data type of the column. In this case since 'species' is composed of strings, the widget an instance of the class ``pn.widgets.Select``.
17
+
18
+
19
+ ```python
20
+ flowers.hvplot.bivariate(x='sepal_width', y='sepal_length', width=600,
21
+ groupby='species')
22
+ ```
23
+
24
+ ### Customizing Widgets
25
+
26
+ We can change where the widget is shown using the ``widget_location`` option.
27
+
28
+
29
+ ```python
30
+ flowers.hvplot.bivariate(x='sepal_width', y='sepal_length', width=600,
31
+ groupby='species', widget_location='left_top')
32
+ ```
33
+
34
+ We can also change what the class of the widget is, using the ``widgets`` dict. For instance if we want to use a slider instead of a selector we can specify that.
35
+
36
+
37
+ ```python
38
+ flowers.hvplot.bivariate(x='sepal_width', y='sepal_length', width=600,
39
+ groupby='species', widgets={'species': pn.widgets.DiscreteSlider})
40
+ ```
41
+
42
+ ### Using widgets as arguments
43
+
44
+ So far we have only been dealing with widgets that are produced when using the ``groupby`` key word. But panel provides many other ways of expanding the interactivity of hvplot objects. For instance we might want to allow the user to select which fields to plot on the ``x`` and ``y`` axes. Or even what ``kind`` of plot to produce.
45
+
46
+
47
+ ```python
48
+ x = pn.widgets.Select(name='x', options=['sepal_width', 'petal_width'])
49
+ y = pn.widgets.Select(name='y', options=['sepal_length', 'petal_length'])
50
+ kind = pn.widgets.Select(name='kind', value='scatter', options=['bivariate', 'scatter'])
51
+
52
+ plot = flowers.hvplot(x=x, y=y, kind=kind, colorbar=False, width=600)
53
+ pn.Row(pn.WidgetBox(x, y, kind), plot)
54
+ ```
55
+
56
+ ### Using functions
57
+
58
+ In addition to using widgets directly as arguments, we can also use functions that have been decorated with ``pn.depends``
59
+
60
+
61
+ ```python
62
+ x = pn.widgets.Select(name='x', options=['sepal_width', 'petal_width'])
63
+ y = pn.widgets.Select(name='y', options=['sepal_length', 'petal_length'])
64
+ kind = pn.widgets.Select(name='kind', value='scatter', options=['bivariate', 'scatter'])
65
+ by_species = pn.widgets.Checkbox(name='By species')
66
+ color = pn.widgets.ColorPicker(value='#ff0000')
67
+
68
+ @pn.depends(by_species, color)
69
+ def by_species_fn(by_species, color):
70
+ return 'species' if by_species else color
71
+
72
+ plot = flowers.hvplot(x=x, y=y, kind=kind, c=by_species_fn, colorbar=False, width=600, legend='top_right')
73
+
74
+ pn.Row(pn.WidgetBox(x, y, kind, color, by_species), plot)
75
+ ```
76
+
77
+ We can keep even add a callback to disable the color options when 'bivariate' is selected. After running the cell below, try changing 'kind' above and notice how the color and 'By species' areas turn grey to indicate that they are disabled.
78
+
79
+
80
+ ```python
81
+ def update(event):
82
+ if kind.value == 'bivariate':
83
+ color.disabled = True
84
+ by_species.disabled = True
85
+ else:
86
+ color.disabled = False
87
+ by_species.disabled = False
88
+
89
+ kind.param.watch(update, 'value');
90
+ ```
91
+
92
+ To learn more about Panel and how to use it with output from hvPlot, see the [Panel docs on the HoloViews pane](https://panel.pyviz.org/reference/panes/HoloViews.html). To learn more about available widgets, see the Widgets' section of the [Panel Reference Gallery](https://panel.pyviz.org/reference/index.html).
hvplot_docs/plot.html ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" >
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>plot</title>
6
+ <link rel="apple-touch-icon" sizes="180x180" href="https://cdn.holoviz.org/panel/1.3.6/dist/images/apple-touch-icon.png">
7
+ <link rel="icon" type="image/png" sizes="32x32" href="https://cdn.holoviz.org/panel/1.3.6/dist/images/favicon.ico"> <style>
8
+ html, body {
9
+ display: flow-root;
10
+ box-sizing: border-box;
11
+ height: 100%;
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+ </style>
16
+ <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.3.2.min.js"></script>
17
+ <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.2.min.js"></script>
18
+ <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.2.min.js"></script>
19
+ <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.2.min.js"></script>
20
+ <script type="text/javascript" src="https://cdn.holoviz.org/panel/1.3.6/dist/panel.min.js"></script>
21
+
22
+ <script type="text/javascript">
23
+ Bokeh.set_log_level("info");
24
+ </script> </head>
25
+ <body>
26
+ <div id="bc2d23bf-3023-4b2e-9825-bb0daf9fdc45" data-root-id="p1579" style="display: contents;"></div>
27
+
28
+ <script type="application/json" id="p1678">
29
+ {"8d93a4b1-0241-4312-97a2-9d7e8cf602d5":{"version":"3.3.2","title":"Bokeh Application","roots":[{"type":"object","name":"Row","id":"p1579","attributes":{"name":"Row01837","tags":["embedded"],"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1669","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},{"type":"object","name":"ImportedStyleSheet","id":"p1670","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"type":"object","name":"ImportedStyleSheet","id":"p1667","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/bundled/theme/default.css"}},{"type":"object","name":"ImportedStyleSheet","id":"p1668","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/bundled/theme/native.css"}}],"min_height":400,"margin":0,"sizing_mode":"stretch_both","align":"start","children":[{"type":"object","name":"Figure","id":"p1593","attributes":{"width":null,"height":null,"min_height":400,"margin":[5,10],"sizing_mode":"stretch_both","align":"start","x_range":{"type":"object","name":"Range1d","id":"p1580","attributes":{"tags":[[["bill_length_mm","bill_length_mm",null]],[]],"start":29.35,"end":62.35,"reset_start":29.35,"reset_end":62.35}},"y_range":{"type":"object","name":"Range1d","id":"p1581","attributes":{"tags":[[["bill_depth_mm","bill_depth_mm",null]],{"type":"map","entries":[["invert_yaxis",false],["autorange",false]]}],"start":12.26,"end":22.34,"reset_start":12.26,"reset_end":22.34}},"x_scale":{"type":"object","name":"LinearScale","id":"p1603"},"y_scale":{"type":"object","name":"LinearScale","id":"p1604"},"title":{"type":"object","name":"Title","id":"p1596","attributes":{"text":"Penguins Scatter","text_color":"black","text_font_size":"12pt"}},"renderers":[{"type":"object","name":"GlyphRenderer","id":"p1633","attributes":{"name":"Adelie","data_source":{"type":"object","name":"ColumnDataSource","id":"p1624","attributes":{"selected":{"type":"object","name":"Selection","id":"p1625","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1626"},"data":{"type":"map","entries":[["bill_length_mm",{"type":"ndarray","array":{"type":"bytes","data":"zczMzMyMQ0AAAAAAAMBDQGZmZmZmJkRAAAAAAAAA+H+amZmZmVlCQGZmZmZmpkNAMzMzMzNzQ0CamZmZmZlDQM3MzMzMDEFAAAAAAAAARUBmZmZmZuZCQGZmZmZm5kJAzczMzMyMREDNzMzMzExDQM3MzMzMTEFAzczMzMxMQkCamZmZmVlDQAAAAAAAQEVAMzMzMzMzQUAAAAAAAABHQGZmZmZm5kJAmpmZmZnZQkAzMzMzM/NBQJqZmZmZGUNAZmZmZmZmQ0BmZmZmZqZBQM3MzMzMTERAAAAAAABAREAzMzMzM/NCQAAAAAAAQERAAAAAAADAQ0CamZmZmZlCQAAAAAAAwENAMzMzMzNzREAzMzMzMzNCQJqZmZmZmUNAZmZmZmZmQ0CamZmZmRlFQM3MzMzMzEJAZmZmZmbmQ0AAAAAAAEBCQGZmZmZmZkRAAAAAAAAAQkDNzMzMzAxGQAAAAAAAgEJAzczMzMzMQ0DNzMzMzIxEQAAAAAAAwEJAAAAAAAAAQkBmZmZmZiZFQM3MzMzMzENAzczMzMwMREAAAAAAAIBBQAAAAAAAAEVAAAAAAABAQUAzMzMzM7NEQAAAAAAAgENAzczMzMxMREAAAAAAAEBCQM3MzMzMzEJAmpmZmZnZQUBmZmZmZqZEQM3MzMzMzEJAzczMzMyMREAzMzMzMzNCQM3MzMzMzERAAAAAAADAQUDNzMzMzIxEQDMzMzMz80FAZmZmZmbmREAAAAAAAMBAQJqZmZmZ2UNAzczMzMzMQ0BmZmZmZuZGQAAAAAAAwEFAZmZmZmZmRUAzMzMzM3NEQJqZmZmZmUJAmpmZmZkZQkDNzMzMzAxFQM3MzMzMTEFAMzMzMzNzRUCamZmZmVlCQM3MzMzMjEFAZmZmZmamQkBmZmZmZqZEQGZmZmZmJkJAMzMzMzNzQkBmZmZmZiZDQDMzMzMzc0NAmpmZmZnZQUDNzMzMzIxEQAAAAAAAAEFAzczMzMzMQ0CamZmZmRlCQGZmZmZmZkRAzczMzMwMQ0BmZmZmZiZEQM3MzMzMjEBAmpmZmZmZRUAAAAAAAIBBQAAAAAAAgERAmpmZmZnZQkBmZmZmZuZCQDMzMzMz80JAmpmZmZnZQ0DNzMzMzExDQJqZmZmZGUNAzczMzMwMQ0CamZmZmZlFQM3MzMzMDENAzczMzMzMRkCamZmZmdlDQJqZmZmZGUVAzczMzMzMQ0CamZmZmVlFQM3MzMzMTENAZmZmZmamQkCamZmZmdlBQM3MzMzMjERAmpmZmZkZQkCamZmZmdlCQJqZmZmZGURAMzMzMzOzRECamZmZmZlBQM3MzMzMTERAZmZmZmZmQ0AAAAAAAMBEQAAAAAAAgENAzczMzMwMRkAAAAAAAEBDQM3MzMzMjEVAZmZmZmZmQkAAAAAAAMBCQM3MzMzMDENAzczMzMyMREDNzMzMzMxBQJqZmZmZGURAAAAAAACAQkCamZmZmdlDQJqZmZmZGURAzczMzMxMREDNzMzMzAxAQJqZmZmZWURAZmZmZmamQkAAAAAAAIBDQJqZmZmZmUNAzczMzMxMQkAAAAAAAABCQGZmZmZm5kJAAAAAAAAAQkAAAAAAAMBEQA=="},"shape":[152],"dtype":"float64","order":"little"}],["bill_depth_mm",{"type":"ndarray","array":{"type":"bytes","data":"MzMzMzOzMkBmZmZmZmYxQAAAAAAAADJAAAAAAAAA+H/NzMzMzEwzQJqZmZmZmTRAzczMzMzMMUCamZmZmZkzQJqZmZmZGTJAMzMzMzMzNECamZmZmRkxQM3MzMzMTDFAmpmZmZmZMUAzMzMzMzM1QJqZmZmZGTVAzczMzMzMMUAAAAAAAAAzQDMzMzMzszRAZmZmZmZmMkAAAAAAAIA1QM3MzMzMTDJAMzMzMzOzMkAzMzMzMzMzQJqZmZmZGTJAMzMzMzMzMUBmZmZmZuYyQJqZmZmZmTJAZmZmZmbmMUCamZmZmZkyQGZmZmZm5jJAMzMzMzOzMECamZmZmRkyQM3MzMzMzDFAZmZmZmbmMkAAAAAAAAAxQJqZmZmZGTVAAAAAAAAANEAAAAAAAIAyQM3MzMzMTDNAmpmZmZkZM0AAAAAAAAAyQGZmZmZmZjJAAAAAAACAMkAzMzMzM7MzQGZmZmZm5jBAzczMzMzMMkAAAAAAAAAzQGZmZmZm5jJAZmZmZmbmMUAzMzMzMzM1QDMzMzMzszFAZmZmZmbmMkBmZmZmZuYxQAAAAAAAgDNAmpmZmZkZMkCamZmZmZkyQAAAAAAAgDFAzczMzMzMMkCamZmZmZkwQJqZmZmZGTNAZmZmZmbmMECamZmZmRk1QAAAAAAAADFAMzMzMzMzMkCamZmZmRkxQAAAAAAAADJAMzMzMzMzMECamZmZmRkzQJqZmZmZmTBAZmZmZmZmM0AAAAAAAAAzQGZmZmZmZjJAMzMzMzMzMUBmZmZmZuYyQAAAAAAAgDFAAAAAAACAMkDNzMzMzMwwQGZmZmZmZjNAmpmZmZkZMECamZmZmRkzQDMzMzMzMzFAmpmZmZmZMUDNzMzMzMwyQGZmZmZmZjNAzczMzMzMMUDNzMzMzEw0QAAAAAAAgDNAmpmZmZmZMkAzMzMzMzMzQM3MzMzMzDJAAAAAAAAAMkCamZmZmRkyQJqZmZmZGTFAmpmZmZkZMkDNzMzMzEwxQGZmZmZm5jJAmpmZmZmZMkAAAAAAAIAyQJqZmZmZGTBAAAAAAACAMkBmZmZmZuYxQAAAAAAAADRAAAAAAAAAMEAAAAAAAAA0QJqZmZmZmTJAZmZmZmbmMkAzMzMzMzMxQAAAAAAAADRAAAAAAAAAMUAAAAAAAAAzQAAAAAAAgDBAzczMzMxMNEAzMzMzM7MxQAAAAAAAgDNAMzMzMzOzNEDNzMzMzEwyQAAAAAAAADFAAAAAAACANEAAAAAAAAAxQJqZmZmZmTJAMzMzMzMzMUDNzMzMzMwzQAAAAAAAADFAAAAAAACAMkDNzMzMzMwvQAAAAAAAADNAmpmZmZmZMUDNzMzMzEwyQJqZmZmZGTFAAAAAAAAAMkBmZmZmZuYxQDMzMzMzMzNAAAAAAACAMkAAAAAAAIAyQJqZmZmZmTFAAAAAAACAMUAAAAAAAIAxQJqZmZmZGTRAAAAAAACAMEBmZmZmZuYxQJqZmZmZGTFAMzMzMzMzMUAAAAAAAAAvQAAAAAAAADFAzczMzMzMMEAzMzMzM7MyQJqZmZmZmTJAZmZmZmZmMkDNzMzMzMwxQJqZmZmZGTJAmpmZmZkZMUAAAAAAAIAyQA=="},"shape":[152],"dtype":"float64","order":"little"}],["species",["Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie","Adelie"]]]}}},"view":{"type":"object","name":"CDSView","id":"p1634","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1635"}}},"glyph":{"type":"object","name":"Scatter","id":"p1630","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#30a2da"},"fill_color":{"type":"value","value":"#30a2da"},"hatch_color":{"type":"value","value":"#30a2da"}}},"selection_glyph":{"type":"object","name":"Scatter","id":"p1638","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"angle":{"type":"value","value":0.0},"line_color":{"type":"value","value":"#30a2da"},"line_alpha":{"type":"value","value":1},"line_width":{"type":"value","value":1},"line_join":{"type":"value","value":"bevel"},"line_cap":{"type":"value","value":"butt"},"line_dash":{"type":"value","value":[]},"line_dash_offset":{"type":"value","value":0},"fill_color":{"type":"value","value":"#30a2da"},"fill_alpha":{"type":"value","value":1},"hatch_color":{"type":"value","value":"#30a2da"},"hatch_alpha":{"type":"value","value":1.0},"hatch_scale":{"type":"value","value":12.0},"hatch_pattern":{"type":"value","value":null},"hatch_weight":{"type":"value","value":1.0},"marker":{"type":"value","value":"circle"}}},"nonselection_glyph":{"type":"object","name":"Scatter","id":"p1631","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#30a2da"},"line_alpha":{"type":"value","value":1},"fill_color":{"type":"value","value":"#30a2da"},"fill_alpha":{"type":"value","value":1},"hatch_color":{"type":"value","value":"#30a2da"},"hatch_alpha":{"type":"value","value":0.1}}},"muted_glyph":{"type":"object","name":"Scatter","id":"p1632","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#30a2da"},"line_alpha":{"type":"value","value":0.2},"fill_color":{"type":"value","value":"#30a2da"},"fill_alpha":{"type":"value","value":0.2},"hatch_color":{"type":"value","value":"#30a2da"},"hatch_alpha":{"type":"value","value":0.2}}}}},{"type":"object","name":"GlyphRenderer","id":"p1648","attributes":{"name":"Chinstrap","data_source":{"type":"object","name":"ColumnDataSource","id":"p1639","attributes":{"selected":{"type":"object","name":"Selection","id":"p1640","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1641"},"data":{"type":"map","entries":[["bill_length_mm",{"type":"ndarray","array":{"type":"bytes","data":"AAAAAABAR0AAAAAAAABJQGZmZmZmpklAMzMzMzOzRkCamZmZmVlKQJqZmZmZmUZAzczMzMwMR0BmZmZmZqZJQAAAAAAAAEdAZmZmZmamSUDNzMzMzExHQJqZmZmZ2UlAAAAAAACAR0AAAAAAAABKQDMzMzMz80ZAAAAAAABASUBmZmZmZiZJQAAAAAAAAE1AMzMzMzMzR0CamZmZmZlIQDMzMzMzM0VAAAAAAABASECamZmZmZlFQM3MzMzMTElAmpmZmZlZR0AAAAAAAABKQAAAAAAAQElAAAAAAADASEAzMzMzMzNHQGZmZmZmZkpAMzMzMzNzRECamZmZmRlLQAAAAAAAQEVAAAAAAACASUCamZmZmdlIQAAAAAAAwEdAzczMzMzMR0AAAAAAAABKQDMzMzMzc0dAAAAAAADASkAAAAAAAIBIQJqZmZmZGUdAMzMzMzNzSUAAAAAAAMBGQDMzMzMzc0lAZmZmZmZmSUDNzMzMzAxJQAAAAAAAgEhAAAAAAADASUBmZmZmZuZIQM3MzMzMDEhAMzMzMzOzSUCamZmZmdlGQJqZmZmZWUlAAAAAAABARUCamZmZmRlKQJqZmZmZmUZAZmZmZmamSECamZmZmRlJQM3MzMzMzEZAMzMzMzPzSUBmZmZmZmZHQJqZmZmZ2UZAZmZmZmbmS0AAAAAAAMBFQM3MzMzMzEhAZmZmZmZmSUCamZmZmRlJQA=="},"shape":[68],"dtype":"float64","order":"little"}],["bill_depth_mm",{"type":"ndarray","array":{"type":"bytes","data":"ZmZmZmbmMUAAAAAAAIAzQDMzMzMzMzNAMzMzMzOzMkDNzMzMzMwzQM3MzMzMzDFAMzMzMzMzMkAzMzMzMzMyQGZmZmZm5jJAZmZmZmbmM0DNzMzMzMwxQM3MzMzMTDRAzczMzMxMMUCamZmZmRkyQJqZmZmZGTFAmpmZmZmZM0AAAAAAAAA0QM3MzMzMzDFAmpmZmZmZMkAzMzMzMzMyQM3MzMzMTDFAAAAAAACAMUCamZmZmZkwQGZmZmZmZjNAZmZmZmbmMUAAAAAAAAAzQGZmZmZmZjJAAAAAAAAAM0DNzMzMzMwxQAAAAAAAADRAmpmZmZmZMEDNzMzMzMw0QDMzMzMzszBAzczMzMzMMkCamZmZmZkyQM3MzMzMzDBAzczMzMxMMkAzMzMzM7M0QJqZmZmZmTBAZmZmZmbmM0AAAAAAAIAzQAAAAAAAgDFAmpmZmZkZM0AAAAAAAAAxQGZmZmZm5jFAAAAAAACAMkBmZmZmZuYxQJqZmZmZmTNAMzMzMzOzMkDNzMzMzEwxQGZmZmZmZjBAAAAAAAAAM0DNzMzMzEwxQDMzMzMzszNAzczMzMxMMUDNzMzMzMwyQJqZmZmZmTBAZmZmZmbmM0DNzMzMzMwyQGZmZmZmZjNAAAAAAACAM0AAAAAAAIAwQAAAAAAAADFAzczMzMzMM0CamZmZmRkyQDMzMzMzMzJAAAAAAAAAM0AzMzMzM7MyQA=="},"shape":[68],"dtype":"float64","order":"little"}],["species",["Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap","Chinstrap"]]]}}},"view":{"type":"object","name":"CDSView","id":"p1649","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1650"}}},"glyph":{"type":"object","name":"Scatter","id":"p1645","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#fc4f30"},"fill_color":{"type":"value","value":"#fc4f30"},"hatch_color":{"type":"value","value":"#fc4f30"}}},"selection_glyph":{"type":"object","name":"Scatter","id":"p1652","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"angle":{"type":"value","value":0.0},"line_color":{"type":"value","value":"#fc4f30"},"line_alpha":{"type":"value","value":1},"line_width":{"type":"value","value":1},"line_join":{"type":"value","value":"bevel"},"line_cap":{"type":"value","value":"butt"},"line_dash":{"type":"value","value":[]},"line_dash_offset":{"type":"value","value":0},"fill_color":{"type":"value","value":"#fc4f30"},"fill_alpha":{"type":"value","value":1},"hatch_color":{"type":"value","value":"#fc4f30"},"hatch_alpha":{"type":"value","value":1.0},"hatch_scale":{"type":"value","value":12.0},"hatch_pattern":{"type":"value","value":null},"hatch_weight":{"type":"value","value":1.0},"marker":{"type":"value","value":"circle"}}},"nonselection_glyph":{"type":"object","name":"Scatter","id":"p1646","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#fc4f30"},"line_alpha":{"type":"value","value":1},"fill_color":{"type":"value","value":"#fc4f30"},"fill_alpha":{"type":"value","value":1},"hatch_color":{"type":"value","value":"#fc4f30"},"hatch_alpha":{"type":"value","value":0.1}}},"muted_glyph":{"type":"object","name":"Scatter","id":"p1647","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#fc4f30"},"line_alpha":{"type":"value","value":0.2},"fill_color":{"type":"value","value":"#fc4f30"},"fill_alpha":{"type":"value","value":0.2},"hatch_color":{"type":"value","value":"#fc4f30"},"hatch_alpha":{"type":"value","value":0.2}}}}},{"type":"object","name":"GlyphRenderer","id":"p1662","attributes":{"name":"Gentoo","data_source":{"type":"object","name":"ColumnDataSource","id":"p1653","attributes":{"selected":{"type":"object","name":"Selection","id":"p1654","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1655"},"data":{"type":"map","entries":[["bill_length_mm",{"type":"ndarray","array":{"type":"bytes","data":"zczMzMwMR0AAAAAAAABJQJqZmZmZWUhAAAAAAAAASUDNzMzMzMxHQAAAAAAAQEdAMzMzMzOzRkCamZmZmVlHQGZmZmZmpkVAZmZmZmZmR0AzMzMzM3NEQAAAAAAAgEhAAAAAAADARkAzMzMzMzNIQGZmZmZm5kZAZmZmZmamSEAAAAAAAABFQJqZmZmZmUhAmpmZmZkZR0CamZmZmVlIQJqZmZmZGUlAzczMzMyMRkAAAAAAAEBHQGZmZmZmJkdAMzMzMzNzRUDNzMzMzAxHQAAAAAAAQEZAZmZmZmbmR0CamZmZmRlIQAAAAAAAAElAZmZmZmamR0BmZmZmZmZFQM3MzMzMjEZAzczMzMzMTUDNzMzMzIxIQDMzMzMzM0hAzczMzMxMRUAzMzMzMzNGQAAAAAAAAEZAmpmZmZlZSECamZmZmVlFQM3MzMzMzEhAZmZmZmamRkDNzMzMzMxIQAAAAAAAQElAzczMzMzMRUAAAAAAAMBGQAAAAAAAQElAMzMzMzNzRkCamZmZmZlGQM3MzMzMTEdAAAAAAABASEDNzMzMzIxGQM3MzMzMDElAAAAAAABAR0AAAAAAAIBGQGZmZmZm5kVAAAAAAADARkCamZmZmZlFQDMzMzMzM0lAZmZmZmamRkCamZmZmRlHQJqZmZmZ2UZAZmZmZmYmS0BmZmZmZuZGQGZmZmZm5khAmpmZmZkZR0AAAAAAAMBIQAAAAAAAwEVAmpmZmZlZSUCamZmZmdlHQDMzMzMzM0dAmpmZmZkZSEAAAAAAAEBHQDMzMzMzM0dAzczMzMxMSEAAAAAAAMBHQM3MzMzMjElAmpmZmZmZRkCamZmZmZlGQM3MzMzMjEhAAAAAAABASkAzMzMzM7NHQAAAAAAAAElAMzMzMzNzRkBmZmZmZmZJQDMzMzMzs0VAZmZmZmamSUAAAAAAAMBHQM3MzMzMDEpAAAAAAADAR0CamZmZmRlKQAAAAAAAwEZAAAAAAADASEAAAAAAAEBGQGZmZmZmZklAMzMzMzOzSEAzMzMzM3NHQDMzMzMzM0hAzczMzMyMSUAAAAAAAEBIQDMzMzMz80tAmpmZmZmZR0DNzMzMzIxIQGZmZmZmpkdAZmZmZmZmR0CamZmZmdlEQDMzMzMzs0pAZmZmZmamRUDNzMzMzAxIQAAAAAAAQElAZmZmZmbmSEAAAAAAAMBFQAAAAAAAwElAmpmZmZkZR0DNzMzMzIxLQAAAAAAAQEZAZmZmZmZmSECamZmZmZlHQAAAAAAAAPh/ZmZmZmZmR0AzMzMzMzNJQJqZmZmZmUZAMzMzMzPzSEA="},"shape":[124],"dtype":"float64","order":"little"}],["bill_depth_mm",{"type":"ndarray","array":{"type":"bytes","data":"ZmZmZmZmKkDNzMzMzEwwQDMzMzMzMyxAZmZmZmZmLkAAAAAAAAAtQAAAAAAAACtAMzMzMzMzLUCamZmZmZkuQM3MzMzMzCpAzczMzMzMLkBmZmZmZmYrQJqZmZmZGTBAZmZmZmZmK0AzMzMzMzMtQDMzMzMzMy1AZmZmZmZmL0AAAAAAAAArQGZmZmZmZi5AAAAAAAAALUAzMzMzMzMuQJqZmZmZmSxAAAAAAAAALUAAAAAAAAAtQJqZmZmZmS9AMzMzMzMzKkAzMzMzMzMuQJqZmZmZmSxAAAAAAAAALkCamZmZmZksQJqZmZmZmS5AmpmZmZmZLkBmZmZmZmYsQAAAAAAAAC1AAAAAAAAAMUCamZmZmZktQM3MzMzMTDBAZmZmZmZmK0DNzMzMzEwxQDMzMzMzMytAZmZmZmZmL0BmZmZmZmYrQAAAAAAAADBAZmZmZmZmK0AAAAAAAAAuQM3MzMzMzC9AzczMzMzMK0DNzMzMzMwrQM3MzMzMzC9AmpmZmZmZKkCamZmZmZkvQGZmZmZmZixAMzMzMzMzLEDNzMzMzMwsQAAAAAAAAC5AzczMzMzMLEDNzMzMzMwuQM3MzMzMzCtAAAAAAAAALkAAAAAAAAAtQJqZmZmZmS5AmpmZmZmZK0DNzMzMzMwtQM3MzMzMzCtAZmZmZmZmL0BmZmZmZmYsQM3MzMzMzDBAzczMzMzMLEAzMzMzMzMwQGZmZmZmZixAAAAAAAAALkAAAAAAAAAuQDMzMzMzMy9AMzMzMzMzL0CamZmZmZktQAAAAAAAAC5AAAAAAAAAMEBmZmZmZmYsQM3MzMzMTDBAmpmZmZmZK0BmZmZmZmYwQAAAAAAAAC1AMzMzMzMzL0AzMzMzMzMtQM3MzMzMzC9AmpmZmZmZK0DNzMzMzEwxQM3MzMzMzCxAZmZmZmZmLEAAAAAAAAAsQAAAAAAAADFAAAAAAAAALkCamZmZmRkxQAAAAAAAAC1AmpmZmZkZMEBmZmZmZmYtQGZmZmZmZi9AmpmZmZmZL0AzMzMzMzMtQM3MzMzMzCxAAAAAAACAMEAAAAAAAAAuQAAAAAAAADFAAAAAAAAAL0AAAAAAAAAuQJqZmZmZmStAmpmZmZkZMEBmZmZmZmYtQJqZmZmZmS9AAAAAAAAALEAzMzMzMzMuQGZmZmZmZi5AzczMzMzML0BmZmZmZmYuQM3MzMzMTDBAMzMzMzMzLEAAAAAAAAAwQGZmZmZmZi9AMzMzMzMzMEBmZmZmZmYrQAAAAAAAAPh/mpmZmZmZLEBmZmZmZmYvQJqZmZmZmS1AmpmZmZkZMEA="},"shape":[124],"dtype":"float64","order":"little"}],["species",["Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo","Gentoo"]]]}}},"view":{"type":"object","name":"CDSView","id":"p1663","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1664"}}},"glyph":{"type":"object","name":"Scatter","id":"p1659","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#e5ae38"},"fill_color":{"type":"value","value":"#e5ae38"},"hatch_color":{"type":"value","value":"#e5ae38"}}},"selection_glyph":{"type":"object","name":"Scatter","id":"p1666","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"angle":{"type":"value","value":0.0},"line_color":{"type":"value","value":"#e5ae38"},"line_alpha":{"type":"value","value":1},"line_width":{"type":"value","value":1},"line_join":{"type":"value","value":"bevel"},"line_cap":{"type":"value","value":"butt"},"line_dash":{"type":"value","value":[]},"line_dash_offset":{"type":"value","value":0},"fill_color":{"type":"value","value":"#e5ae38"},"fill_alpha":{"type":"value","value":1},"hatch_color":{"type":"value","value":"#e5ae38"},"hatch_alpha":{"type":"value","value":1.0},"hatch_scale":{"type":"value","value":12.0},"hatch_pattern":{"type":"value","value":null},"hatch_weight":{"type":"value","value":1.0},"marker":{"type":"value","value":"circle"}}},"nonselection_glyph":{"type":"object","name":"Scatter","id":"p1660","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#e5ae38"},"line_alpha":{"type":"value","value":1},"fill_color":{"type":"value","value":"#e5ae38"},"fill_alpha":{"type":"value","value":1},"hatch_color":{"type":"value","value":"#e5ae38"},"hatch_alpha":{"type":"value","value":0.1}}},"muted_glyph":{"type":"object","name":"Scatter","id":"p1661","attributes":{"tags":["apply_ranges"],"x":{"type":"field","field":"bill_length_mm"},"y":{"type":"field","field":"bill_depth_mm"},"size":{"type":"value","value":5.477225575051661},"line_color":{"type":"value","value":"#e5ae38"},"line_alpha":{"type":"value","value":0.2},"fill_color":{"type":"value","value":"#e5ae38"},"fill_alpha":{"type":"value","value":0.2},"hatch_color":{"type":"value","value":"#e5ae38"},"hatch_alpha":{"type":"value","value":0.2}}}}}],"toolbar":{"type":"object","name":"Toolbar","id":"p1602","attributes":{"tools":[{"type":"object","name":"WheelZoomTool","id":"p1585","attributes":{"tags":["hv_created"],"renderers":"auto","zoom_together":"none"}},{"type":"object","name":"HoverTool","id":"p1586","attributes":{"tags":["hv_created"],"renderers":[{"id":"p1633"},{"id":"p1648"},{"id":"p1662"}],"tooltips":[["species","@{species}"],["bill_length_mm","@{bill_length_mm}"],["bill_depth_mm","@{bill_depth_mm}"]]}},{"type":"object","name":"SaveTool","id":"p1615"},{"type":"object","name":"PanTool","id":"p1616"},{"type":"object","name":"BoxZoomTool","id":"p1617","attributes":{"overlay":{"type":"object","name":"BoxAnnotation","id":"p1618","attributes":{"syncable":false,"level":"overlay","visible":false,"left":{"type":"number","value":"nan"},"right":{"type":"number","value":"nan"},"top":{"type":"number","value":"nan"},"bottom":{"type":"number","value":"nan"},"left_units":"canvas","right_units":"canvas","top_units":"canvas","bottom_units":"canvas","line_color":"black","line_alpha":1.0,"line_width":2,"line_dash":[4,4],"fill_color":"lightgrey","fill_alpha":0.5}}}},{"type":"object","name":"ResetTool","id":"p1623"}],"active_drag":{"id":"p1616"},"active_scroll":{"id":"p1585"}}},"left":[{"type":"object","name":"LinearAxis","id":"p1610","attributes":{"ticker":{"type":"object","name":"BasicTicker","id":"p1611","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1612"},"axis_label":"bill_depth_mm","major_label_policy":{"type":"object","name":"AllLabels","id":"p1613"}}}],"right":[{"type":"object","name":"Legend","id":"p1636","attributes":{"location":[0,0],"title":"species","click_policy":"mute","items":[{"type":"object","name":"LegendItem","id":"p1637","attributes":{"label":{"type":"value","value":"Adelie"},"renderers":[{"id":"p1633"}]}},{"type":"object","name":"LegendItem","id":"p1651","attributes":{"label":{"type":"value","value":"Chinstrap"},"renderers":[{"id":"p1648"}]}},{"type":"object","name":"LegendItem","id":"p1665","attributes":{"label":{"type":"value","value":"Gentoo"},"renderers":[{"id":"p1662"}]}}]}}],"below":[{"type":"object","name":"LinearAxis","id":"p1605","attributes":{"ticker":{"type":"object","name":"BasicTicker","id":"p1606","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1607"},"axis_label":"bill_length_mm","major_label_policy":{"type":"object","name":"AllLabels","id":"p1608"}}}],"center":[{"type":"object","name":"Grid","id":"p1609","attributes":{"axis":{"id":"p1605"},"grid_line_color":null}},{"type":"object","name":"Grid","id":"p1614","attributes":{"dimension":1,"axis":{"id":"p1610"},"grid_line_color":null}}],"min_border_top":10,"min_border_bottom":10,"min_border_left":10,"min_border_right":10,"output_backend":"webgl"}}]}}],"defs":[{"type":"model","name":"ReactiveHTML1"},{"type":"model","name":"FlexBox1","properties":[{"name":"align_content","kind":"Any","default":"flex-start"},{"name":"align_items","kind":"Any","default":"flex-start"},{"name":"flex_direction","kind":"Any","default":"row"},{"name":"flex_wrap","kind":"Any","default":"wrap"},{"name":"justify_content","kind":"Any","default":"flex-start"}]},{"type":"model","name":"FloatPanel1","properties":[{"name":"config","kind":"Any","default":{"type":"map"}},{"name":"contained","kind":"Any","default":true},{"name":"position","kind":"Any","default":"right-top"},{"name":"offsetx","kind":"Any","default":null},{"name":"offsety","kind":"Any","default":null},{"name":"theme","kind":"Any","default":"primary"},{"name":"status","kind":"Any","default":"normalized"}]},{"type":"model","name":"GridStack1","properties":[{"name":"mode","kind":"Any","default":"warn"},{"name":"ncols","kind":"Any","default":null},{"name":"nrows","kind":"Any","default":null},{"name":"allow_resize","kind":"Any","default":true},{"name":"allow_drag","kind":"Any","default":true},{"name":"state","kind":"Any","default":[]}]},{"type":"model","name":"drag1","properties":[{"name":"slider_width","kind":"Any","default":5},{"name":"slider_color","kind":"Any","default":"black"},{"name":"value","kind":"Any","default":50}]},{"type":"model","name":"click1","properties":[{"name":"terminal_output","kind":"Any","default":""},{"name":"debug_name","kind":"Any","default":""},{"name":"clears","kind":"Any","default":0}]},{"type":"model","name":"copy_to_clipboard1","properties":[{"name":"fill","kind":"Any","default":"none"},{"name":"value","kind":"Any","default":null}]},{"type":"model","name":"FastWrapper1","properties":[{"name":"object","kind":"Any","default":null},{"name":"style","kind":"Any","default":null}]},{"type":"model","name":"NotificationAreaBase1","properties":[{"name":"js_events","kind":"Any","default":{"type":"map"}},{"name":"position","kind":"Any","default":"bottom-right"},{"name":"_clear","kind":"Any","default":0}]},{"type":"model","name":"NotificationArea1","properties":[{"name":"js_events","kind":"Any","default":{"type":"map"}},{"name":"notifications","kind":"Any","default":[]},{"name":"position","kind":"Any","default":"bottom-right"},{"name":"_clear","kind":"Any","default":0},{"name":"types","kind":"Any","default":[{"type":"map","entries":[["type","warning"],["background","#ffc107"],["icon",{"type":"map","entries":[["className","fas fa-exclamation-triangle"],["tagName","i"],["color","white"]]}]]},{"type":"map","entries":[["type","info"],["background","#007bff"],["icon",{"type":"map","entries":[["className","fas fa-info-circle"],["tagName","i"],["color","white"]]}]]}]}]},{"type":"model","name":"Notification","properties":[{"name":"background","kind":"Any","default":null},{"name":"duration","kind":"Any","default":3000},{"name":"icon","kind":"Any","default":null},{"name":"message","kind":"Any","default":""},{"name":"notification_type","kind":"Any","default":null},{"name":"_destroyed","kind":"Any","default":false}]},{"type":"model","name":"TemplateActions1","properties":[{"name":"open_modal","kind":"Any","default":0},{"name":"close_modal","kind":"Any","default":0}]},{"type":"model","name":"BootstrapTemplateActions1","properties":[{"name":"open_modal","kind":"Any","default":0},{"name":"close_modal","kind":"Any","default":0}]},{"type":"model","name":"MaterialTemplateActions1","properties":[{"name":"open_modal","kind":"Any","default":0},{"name":"close_modal","kind":"Any","default":0}]}]}}
30
+ </script>
31
+ <script type="text/javascript">
32
+ (function() {
33
+ const fn = function() {
34
+ Bokeh.safely(function() {
35
+ (function(root) {
36
+ function embed_document(root) {
37
+ const docs_json = document.getElementById('p1678').textContent;
38
+ const render_items = [{"docid":"8d93a4b1-0241-4312-97a2-9d7e8cf602d5","roots":{"p1579":"bc2d23bf-3023-4b2e-9825-bb0daf9fdc45"},"root_ids":["p1579"]}];
39
+ root.Bokeh.embed.embed_items(docs_json, render_items);
40
+ }
41
+ if (root.Bokeh !== undefined) {
42
+ embed_document(root);
43
+ } else {
44
+ let attempts = 0;
45
+ const timer = setInterval(function(root) {
46
+ if (root.Bokeh !== undefined) {
47
+ clearInterval(timer);
48
+ embed_document(root);
49
+ } else {
50
+ attempts++;
51
+ if (attempts > 100) {
52
+ clearInterval(timer);
53
+ console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
54
+ }
55
+ }
56
+ }, 10, root)
57
+ }
58
+ })(window);
59
+ });
60
+ };
61
+ if (document.readyState != "loading") fn();
62
+ else document.addEventListener("DOMContentLoaded", fn);
63
+ })();
64
+ </script>
65
+ </body>
66
+ </html>