Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Spikes Element #346

Merged
merged 16 commits into from
Dec 11, 2015
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions doc/Tutorials/Elements.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
" <dt>[``Scatter``](#Scatter)</dt><dd>Discontinuous collection of points indexed over a single dimension.</dd>\n",
" <dt>[``Points``](#Points)</dt><dd>Discontinuous collection of points indexed over two dimensions.</dd>\n",
" <dt>[``VectorField``](#VectorField)</dt><dd>Cyclic variable (and optional auxiliary data) distributed over two-dimensional space.</dd>\n",
" <dt>[``Spikes``](#Spikes)</dt><dd>A collection of horizontal or vertical lines at various locations with fixed height (1D) or variable height (2D).</dd>\n",
" <dt>[``SideHistogram``](#SideHistogram)</dt><dd>Histogram binning data contained by some other ``Element``.</dd>\n",
" </dl>\n",
"\n",
Expand Down Expand Up @@ -461,6 +462,95 @@
"points + points[0.3:0.7, 0.3:0.7].hist()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ``Spikes`` <a id='Spikes'></a>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Spikes represent any number of horizontal or vertical line segments with fixed or variable heights. There are a number of uses for this type, first of all they may be used as a rugplot to give an overview of a one-dimensional distribution. They may also be useful in more domain specific cases, such as visualizing spike trains for neurophysiology or spectrograms in physics and chemistry applications.\n",
"\n",
"In the simplest case a Spikes object therefore represents a 1D distribution:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%%opts Spikes [spike_height=0.1] (alpha=0.4)\n",
"xs = np.random.rand(50)\n",
"ys = np.random.rand(50)\n",
"hv.Points((xs, ys)) * hv.Spikes(xs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When supplying two dimensions to the Spikes object the second dimension will be mapped onto the line height. Optionally you may also supply a cmap and color_index to map color onto one of the dimensions. This way we can for example plot a mass spectrogram:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%%opts Spikes (cmap='Blues')\n",
"hv.Spikes(np.random.rand(20, 2), kdims=['Mass'], vdims=['Intensity'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Another possibility is to draw a number of spike trains as you would encounter in neuroscience:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%%opts Spikes [spike_height=0.1] NdOverlay [show_legend=False]\n",
"hv.NdOverlay({i: hv.Spikes(np.random.rand(10, 1))(plot=dict(yposition=0.1*i))\n",
" for i in range(10)})(plot=dict(yticks=[((i+1)*0.1-0.05, i)for i in range(10)]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally we may use ``Spikes`` to adjoin to a regular plot to visualize marginal distributions:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%%opts Spikes (alpha=0.4) [bgcolor='w'] AdjointLayout [border_size=0]\n",
"points = hv.Points(np.random.randn(100, 2))\n",
"points << hv.Spikes(points['y']) << hv.Spikes(points['x'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
20 changes: 20 additions & 0 deletions holoviews/element/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,23 @@ def __init__(self, data, **params):
if isinstance(data, list) and all(isinstance(d, np.ndarray) for d in data):
data = np.column_stack([d.flat if d.ndim > 1 else d for d in data])
super(VectorField, self).__init__(data, **params)



class Spikes(Chart):
"""
Spikes is a 1D or 2D Element, which represents vertical or
horizontal ticks along some dimension. If an additional dimension
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me 'ticks' makes me think about plotting... how about:

"Spikes is a 1D or 2D Element, which represents a series of vertical or horizontal lines distributed along some dimension..."

is supplied it will be used to specify the height of the
lines. The Element may therefore be used to represent 1D
distributions, spectrograms or spike trains in electrophysiology.
"""

group = param.String(default='Spikes', constant=True)

kdims = param.List(default=[Dimension('x')])

vdims = param.List(default=[])

_1d = True

7 changes: 4 additions & 3 deletions holoviews/plotting/bokeh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from ...element import (Curve, Points, Scatter, Image, Raster, Path,
RGB, Histogram, Spread, HeatMap, Contours,
Path, Box, Bounds, Ellipse, Polygons,
ErrorBars, Text, HLine, VLine, Spline,
ErrorBars, Text, HLine, VLine, Spline, Spikes,
Table, ItemTable, Surface, Scatter3D, Trisurface)
from ...core.options import Options, Cycle, OptionTree
from ...interface import DFrame
Expand All @@ -14,7 +14,7 @@
from .callbacks import Callbacks
from .element import OverlayPlot, BokehMPLWrapper, BokehMPLRawWrapper
from .chart import (PointPlot, CurvePlot, SpreadPlot, ErrorPlot, HistogramPlot,
AdjointHistogramPlot)
AdjointHistogramPlot, SpikesPlot)
from .path import PathPlot, PolygonPlot
from .plot import GridPlot, LayoutPlot, AdjointLayoutPlot
from .raster import RasterPlot, RGBPlot, HeatmapPlot
Expand All @@ -37,6 +37,7 @@
Scatter: PointPlot,
ErrorBars: ErrorPlot,
Spread: SpreadPlot,
Spikes: SpikesPlot,

# Rasters
Image: RasterPlot,
Expand Down Expand Up @@ -81,7 +82,7 @@


AdjointLayoutPlot.registry[Histogram] = AdjointHistogramPlot

AdjointLayoutPlot.registry[Spikes] = SpikesPlot

try:
from ..mpl.seaborn import TimeSeriesPlot, BivariatePlot, DistributionPlot
Expand Down
50 changes: 50 additions & 0 deletions holoviews/plotting/bokeh/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,53 @@ def get_data(self, element, ranges=None):
err_xs.append((x, x))
err_ys.append((y - neg, y + pos))
return (dict(xs=err_xs, ys=err_ys), self._mapping)


class SpikesPlot(PathPlot):

color_index = param.Integer(default=1, doc="""
Index of the dimension from which the color will the drawn""")

spike_height = param.Number(default=0.1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better name for this would be nice although I don't have any better suggestions right now... spike_length maybe as that doesn't imply a direction?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly better. Will use that unless someone comes up with a better suggestion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point; sounds good.


yposition = param.Number(default=0.)

style_opts = (['cmap', 'palette'] + line_properties)

def get_extents(self, element, ranges):
l, b, r, t = super(SpikesPlot, self).get_extents(element, ranges)
if len(element.dimensions()) == 1:
b, t = self.yposition, self.yposition+self.spike_height
return l, b, r, t


def get_data(self, element, ranges=None):
style = self.style[self.cyclic_index]
dims = element.dimensions(label=True)

ypos = self.yposition
if len(dims) > 1:
xs, ys = zip(*(((x, x), (ypos, ypos+y))
for x, y in element.array()))
mapping = dict(xs=dims[0], ys=dims[1])
keys = (dims[0], dims[1])
else:
height = self.spike_height
xs, ys = zip(*(((x[0], x[0]), (ypos, ypos+height))
for x in element.array()))
mapping = dict(xs=dims[0], ys='heights')
keys = (dims[0], 'heights')
if self.invert_axes: keys = keys[::-1]
data = dict(zip(keys, (xs, ys)))

cmap = style.get('palette', style.get('cmap', None))
if self.color_index < len(dims) and cmap:
cdim = dims[self.color_index]
map_key = 'color_' + cdim
mapping['color'] = map_key
cmap = get_cmap(cmap)
colors = element.dimension_values(cdim)
crange = ranges.get(cdim, None)
data[map_key] = map_colors(colors, crange, cmap)

return data, mapping
7 changes: 4 additions & 3 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class ElementPlot(BokehPlot, GenericElementPlot):

{'ticks': '20pt', 'title': '15pt', 'ylabel': '5px', 'xlabel': '5px'}""")

invert_axes = param.Boolean(default=False, doc="""
Whether to invert the x- and y-axis""")

invert_xaxis = param.Boolean(default=False, doc="""
Whether to invert the plot x-axis.""")

Expand Down Expand Up @@ -137,9 +140,7 @@ class ElementPlot(BokehPlot, GenericElementPlot):
# instance attribute.
_update_handles = ['source', 'glyph']

def __init__(self, element, plot=None, invert_axes=False,
show_labels=['x', 'y'], **params):
self.invert_axes = invert_axes
def __init__(self, element, plot=None, show_labels=['x', 'y'], **params):
self.show_labels = show_labels
self.current_ranges = None
super(ElementPlot, self).__init__(element, **params)
Expand Down
4 changes: 3 additions & 1 deletion holoviews/plotting/mpl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def grid_selector(grid):
VectorField: VectorFieldPlot,
ErrorBars: ErrorPlot,
Spread: SpreadPlot,
Spikes: SpikesPlot,

# General plots
GridSpace: GridPlot,
Expand Down Expand Up @@ -157,7 +158,8 @@ def grid_selector(grid):


MPLPlot.sideplots.update({Histogram: SideHistogramPlot,
GridSpace: GridPlot})
GridSpace: GridPlot,
Spikes: MarginalRugPlot})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the word Marginal? That depends on whether RugPlot can be used without being a side plot or not. If it is always a side-plot, RugPlot would do...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really it's just a subclass with different defaults, so it could be used separately but probably won't be in practice.


options = Store.options(backend='matplotlib')

Expand Down
Loading