Skip to content

Commit

Permalink
Add Slope annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Oct 2, 2019
1 parent 361f1a0 commit 10515f7
Show file tree
Hide file tree
Showing 8 changed files with 321 additions and 8 deletions.
88 changes: 88 additions & 0 deletions examples/reference/elements/bokeh/Slope.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### **Title**: Slope Element\n",
"\n",
"**Dependencies**: Bokeh\n",
"\n",
"**Backends**: [Matplotlib](../matplotlib/VLine.ipynb), [Bokeh](./VLine.ipynb)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import holoviews as hv\n",
"from holoviews import opts\n",
"\n",
"hv.extension('bokeh')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ``Slope`` element is a type of annotation that plots a line with arbitrary slope and y-intercept.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"gradient = 2\n",
"y_intercept = 15\n",
"\n",
"# create random data\n",
"xpts = np.arange(0, 20)\n",
"ypts = gradient * xpts + y_intercept + np.random.normal(0, 4, 20)\n",
"\n",
"scatter = hv.Scatter((xpts, ypts))\n",
"slope = hv.Slope(gradient, y_intercept)\n",
"\n",
"scatter.opts(size=10) * slope.opts(color='red', line_width=6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `Slope` maybe also be directly be calculated from a set of Scatter points using the ``Slope.from_scatter`` method, which will infer the gradient and y-intercept automatically:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"normal = hv.Scatter(np.random.randn(20, 2))\n",
"\n",
"normal.opts(size=10) * hv.Slope.from_scatter(normal)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For full documentation and the available style and plot options, use ``hv.help(hv.Slope).``"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
88 changes: 88 additions & 0 deletions examples/reference/elements/matplotlib/Slope.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### **Title**: Slope Element\n",
"\n",
"**Dependencies**: Matplotlib\n",
"\n",
"**Backends**: [Bokeh](../bokeh/VLine.ipynb), [Matplotlib](./VLine.ipynb)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import holoviews as hv\n",
"from holoviews import opts\n",
"\n",
"hv.extension('matplotlib')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ``Slope`` element is a type of annotation that plots a line with arbitrary slope and y-intercept.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"gradient = 2\n",
"y_intercept = 15\n",
"\n",
"# create random data\n",
"xpts = np.arange(0, 20)\n",
"ypts = gradient * xpts + y_intercept + np.random.normal(0, 4, 20)\n",
"\n",
"scatter = hv.Scatter((xpts, ypts))\n",
"slope = hv.Slope(gradient, y_intercept)\n",
"\n",
"scatter * slope.opts(color='red', linewidth=6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `Slope` maybe also be directly be calculated from a set of Scatter points using the ``Slope.from_scatter`` method, which will infer the gradient and y-intercept automatically:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"normal = hv.Scatter(np.random.randn(20, 2))\n",
"\n",
"normal * hv.Slope.from_scatter(normal)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For full documentation and the available style and plot options, use ``hv.help(hv.Slope).``"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
52 changes: 51 additions & 1 deletion holoviews/element/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,57 @@ def dimension_values(self, dimension, expanded=True, flat=True):
return super(HLine, self).dimension_values(dimension)


class Slope(Annotation):
"""A line drawn with arbitrary slope and y-intercept"""

slope = param.Number(default=0)

y_intercept = param.Number(default=0)

def __init__(self, slope, y_intercept, kdims=None, vdims=None, **params):
super(Slope, self).__init__(
(slope, y_intercept), slope=slope, y_intercept=y_intercept,
kdims=kdims, vdims=vdims, **params)


@classmethod
def from_scatter(cls, element, **kwargs):
"""Returns a Slope element given an element of x/y-coordinates
Computes the slope and y-intercept from an element containing
x- and y-coordinates.
Args:
element: Element to compute slope from
kwargs: Keyword arguments to pass to the Slope element
Returns:
Slope element
"""
x, y = (element.dimension_values(i) for i in range(2))
par = np.polyfit(x, y, 1, full=True)
gradient=par[0][0]
y_intercept=par[0][1]
return cls(gradient, y_intercept, **kwargs)


def clone(self, data=None, shared_data=True, new_type=None, *args, **overrides):
"""Clones the object, overriding data and parameters.
Args:
data: New data replacing the existing data
shared_data (bool, optional): Whether to use existing data
new_type (optional): Type to cast object to
*args: Additional arguments to pass to constructor
**overrides: New keyword arguments to pass to constructor
Returns:
Cloned Slope
"""
return Element2D.clone(self, (self.slope, self.y_intercept), shared_data, new_type,
*args, **overrides)



class VSpan(Annotation):
"""Vertical span annotation at the given position."""
Expand Down Expand Up @@ -429,4 +480,3 @@ class Labels(Dataset, Element2D):

vdims = param.List([Dimension('Label')], bounds=(1, None), doc="""
Defines the value dimension corresponding to the label text.""")

10 changes: 7 additions & 3 deletions holoviews/plotting/bokeh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Table, ItemTable, Area, HSV, QuadMesh, VectorField,
Graph, Nodes, EdgePaths, Distribution, Bivariate,
TriMesh, Violin, Chord, Div, HexTiles, Labels, Sankey,
Tiles, Segments)
Tiles, Segments, Slope)
from ...core.options import Options, Cycle, Palette
from ...core.util import LooseVersion, VersionError

Expand All @@ -27,8 +27,10 @@
except:
DFrame = None

from .annotation import (TextPlot, LineAnnotationPlot, BoxAnnotationPlot, SplinePlot,
ArrowPlot, DivPlot, LabelsPlot)
from .annotation import (
TextPlot, LineAnnotationPlot, BoxAnnotationPlot, SplinePlot, ArrowPlot,
DivPlot, LabelsPlot, SlopePlot
)
from ..plot import PlotSelector
from .callbacks import Callback # noqa (API import)
from .element import OverlayPlot, ElementPlot
Expand Down Expand Up @@ -100,6 +102,7 @@
VLine: LineAnnotationPlot,
HSpan: BoxAnnotationPlot,
VSpan: BoxAnnotationPlot,
Slope: SlopePlot,
Text: TextPlot,
Labels: LabelsPlot,
Spline: SplinePlot,
Expand Down Expand Up @@ -202,6 +205,7 @@ def colormap_generator(palette):
# Annotations
options.HLine = Options('style', color=Cycle(), line_width=3, alpha=1)
options.VLine = Options('style', color=Cycle(), line_width=3, alpha=1)
options.Slope = Options('style', color=Cycle(), line_width=3, alpha=1)
options.VSpan = Options('style', color=Cycle(), alpha=0.5)
options.HSpan = Options('style', color=Cycle(), alpha=0.5)
options.Arrow = Options('style', arrow_size=10)
Expand Down
34 changes: 33 additions & 1 deletion holoviews/plotting/bokeh/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import param
import numpy as np
from bokeh.models import BoxAnnotation, Span, Arrow, Div as BkDiv
from bokeh.models import BoxAnnotation, Span, Arrow, Div as BkDiv, Slope

try:
from bokeh.models.arrow_heads import TeeHead, NormalHead
arrow_start = {'<->': NormalHead, '<|-|>': NormalHead}
Expand Down Expand Up @@ -201,6 +202,37 @@ def _init_glyph(self, plot, mapping, properties):
return None, box


class SlopePlot(ElementPlot, AnnotationPlot):

style_opts = line_properties + ['level']

_plot_methods = dict(single='Slope')

def get_data(self, element, ranges, style):
data, mapping = {}, {}
gradient, intercept = element.data
if self.invert_axes:
if gradient == 0:
gradient = np.inf, np.inf
else:
gradient, intercept = 1/gradient, -(intercept/gradient)
mapping['gradient'] = gradient
mapping['y_intercept'] = intercept
return (data, mapping, style)

def _init_glyph(self, plot, mapping, properties):
"""
Returns a Bokeh glyph object.
"""
slope = Slope(level=properties.get('level', 'glyph'), **mapping)
plot.add_layout(slope)
return None, slope

def get_extents(self, element, ranges=None, range_type='combined'):
return None, None, None, None



class SplinePlot(ElementPlot, AnnotationPlot):
"""
Draw the supplied Spline annotation (see Spline docstring).
Expand Down
6 changes: 3 additions & 3 deletions holoviews/plotting/bokeh/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ def get_data(self, element, ranges, style):
data = {}

if not self.static_source or self.batched:
xdim, ydim = dims[xidx], dims[yidx]
data[xdim] = element.dimension_values(xidx)
data[ydim] = element.dimension_values(yidx)
xdim, ydim = dims[:2]
data[xdim] = element.dimension_values(xdim)
data[ydim] = element.dimension_values(ydim)
self._categorize_data(data, dims[:2], element.dimensions())

cdata, cmapping = self._get_color_data(element, ranges, style)
Expand Down
2 changes: 2 additions & 0 deletions holoviews/plotting/mpl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def grid_selector(grid):
HLine: HLinePlot,
VSpan: VSpanPlot,
HSpan: HSpanPlot,
Slope: SlopePlot,
Arrow: ArrowPlot,
Spline: SplinePlot,
Text: TextPlot,
Expand Down Expand Up @@ -262,6 +263,7 @@ def grid_selector(grid):
# Annotations
options.VLine = Options('style', color=Cycle())
options.HLine = Options('style', color=Cycle())
options.Slope = Options('style', color=Cycle())
options.VSpan = Options('style', alpha=0.5, facecolor=Cycle())
options.HSpan = Options('style', alpha=0.5, facecolor=Cycle())
if config.style_17:
Expand Down
Loading

0 comments on commit 10515f7

Please sign in to comment.