import numpy as np
import pandas as pd
import holoviews as hv
hv.extension('bokeh', 'matplotlib')
%opts Ellipse [xaxis=None yaxis=None] (color='red' line_width=2)
%opts Box [xaxis=None yaxis=None] (color='blue' line_width=2)

"""
Explanation: <a href='http://www.holoviews.org'><img src="assets/hv+bk.png" alt="HV+BK logos" width="40%;" align="left"/></a>
<div style="float:right;"><h2>06. Custom Interactivity</h2></div>

In the exploring with containers section, the DynamicMap container was introduced. In that section, the arguments to the callable returning elements were supplied by HoloViews sliders. In this section, we will generalize the ways in which you can generate values to update a DynamicMap.
End of explanation
"""


def annotations(angle):
    radians = (angle / 180) * np.pi
    return (hv.Box(0,0,4, orientation=np.pi/4) 
            * hv.Ellipse(0,0,(2,4), orientation=radians) 
            * hv.Text(0,0,'{0}º'.format(float(angle))))

hv.DynamicMap(annotations, kdims=['angle']).redim.range(angle=(0, 360)).redim.label(angle='angle (º)')

"""
Explanation: A simple DynamicMap
Let us now create a simple DynamicMap using three annotation elements, namely Box, Text, and Ellipse:
End of explanation
"""


from holoviews import streams
from holoviews.streams import Stream
Angle = Stream.define('Angle', angle=0)

"""
Explanation: This example uses the concepts introduced  in the exploring with containers section. As before, the argument angle is supplied by the position of the 'angle' slider.
Introducing Streams
HoloViews offers a way of supplying the angle value to our annotation function through means other than sliders, namely via the streams system which you can learn about in the user guide.
All stream classes are found in the streams submodule and are subclasses of Stream. You can use Stream directly to make custom stream classes via the define classmethod:
End of explanation
"""


hv.help(Angle)

"""
Explanation: Here Angle is capitalized as it is a subclass of Stream with a numeric angle parameter, which has a default value of zero. You can verify this using hv.help:
End of explanation
"""


%%opts Box (color='green')
dmap=hv.DynamicMap(annotations, streams=[Angle(angle=45)])
dmap

"""
Explanation: Now we can declare a DynamicMap where instead of specifying kdims, we instantiate Angle with an angle of 45º and pass it to the streams parameter of the DynamicMap:
End of explanation
"""


dmap.event(angle=90)

"""
Explanation: As expected, we see our ellipse with an angle of 45º as specified via the angle parameter of our Angle instance. In itself, this wouldn't be very useful but given that we have a handle on our DynamicMap dmap, we can now use the event method to update the angle parameter value and update the plot:
End of explanation
"""


# Exercise: Regenerate the DynamicMap, initializing the angle to 15 degrees


# Exercise: Use dmap.event to set the angle shown to 145 degrees.


# Exercise: Do not specify an initial angle so that the default value of 0 degrees is used.


# Exercise: Use the cell magic %%output backend='matplotlib' to try the above with matplotlib


# Exercise: Declare a DynamicMap using annotations2 and AngleAndSize
# Then use the event method to set the size to 1.5 and the angle to 30 degrees
def annotations2(angle, size):
    radians = (angle / 180.) * np.pi
    return (hv.Box(0,0,4, orientation=np.pi/4) 
            * hv.Ellipse(0,0,(size,size*2), orientation=radians) 
            * hv.Text(0,0,'{0}º'.format(float(angle))))

AngleAndSize = Stream.define('AngleAndSize', angle=0., size=1.)

"""
Explanation: When running this cell, the visualization above will jump to the 90º position! If you have already run the cell, just change the value above and re-run, and you'll see the plot above update.
This simple example shows how you can use the event method to update a visualization with any value you can generate in Python.
End of explanation
"""


%%opts Ellipse (color='orange')
dmap2=hv.DynamicMap(annotations, streams=[Angle(angle=0)])
dmap2

dmap2.periodic(0.01, count=180, timeout=8, param_fn=lambda i: {'angle':i})

"""
Explanation: Periodic updates
Using streams you can animate your visualizations by driving them with events from Python. Of course, you could use loops to call the event method, but this approach can queue up events much faster than they can be visualized. Instead of inserting sleeps into your loops to avoid that problem, it is recommended you use the periodic method, which lets you specify a time period between updates (in seconds):
End of explanation
"""


# Exercise: Experiment with different period values. How fast can things update?


# Exercise: Increase count so that the oval completes a full rotation.


# Exercise: Lower the timeout so the oval completes less than a quarter turn before stopping


"""
Explanation: If you re-execute the above cell, you should see the preceding plot update continuously until the count value is reached.
End of explanation
"""


%%opts HLine [xaxis=None yaxis=None]
pointer = streams.PointerXY(x=0, y=0)

def crosshair(x, y):
    return  hv.Ellipse(0,0,1) * hv.HLine(y) * hv.VLine(x)

hv.DynamicMap(crosshair, streams=[pointer])

"""
Explanation: Linked streams
Often, you will want to tie streams to specific user actions in the live JavaScript interface. There are no limitations on how you can generate updated stream parameters values in Python, and so you could manually support updating streams from JavaScript as long as it can communicate with Python to trigger an appropriate stream update. But as Python programmers, we would rather avoid writing JavaScript directly, so HoloViews supports the concept of linked stream classes where possible.
Currently, linked streams are only supported by the Bokeh plotting extension, because only Bokeh executes JavaScript in the notebook and has a suitable event system necessary to enable linked streams (matplotlib displays output as static PNG or SVG in the browser). Here is a simple linked stream example:
End of explanation
"""


# Exercise: Set the defaults so that the crosshair initializes at x=0.25, y=0.25


# Exercise: Copy the above example and adapt it to make a red point of size 10 follow your cursor (using hv.Points)


"""
Explanation: When hovering in the plot above when backed by a live Python process, the crosshair will track the cursor. 
The way it works is very simple: the crosshair function puts a crosshair at whatever x,y location it is given,  the pointer object supplies a stream of x,y values based on the mouse pointer location, and the DynamicMap object connects the pointer stream's x,y values to the crosshair function to generate the resulting plots.
End of explanation
"""


%%opts Scatter[width=900 height=400 tools=['xbox_select'] ] (cmap='RdBu' line_color='black' size=5 line_width=0.5)
%%opts Scatter [color_index='latitude' colorbar=True colorbar_position='bottom' colorbar_opts={'title': 'Latitude'}]

eclipses = pd.read_csv('../data/eclipses_21C.csv', parse_dates=['date'])
magnitudes = hv.Scatter(eclipses, kdims=['hour_local'], vdims=['magnitude','latitude'])

def selection_example(index):
    text = '{0} eclipses'.format(len(index)) if index else ''
    return magnitudes * hv.Text(2,1, text)

dmap3 = hv.DynamicMap(selection_example, streams=[streams.Selection1D()])
dmap3.redim.label(magnitude='Eclipse Magnitude', hour_local='Hour (local time)')

"""
Explanation: You can view other similar examples of custom interactivity in our reference gallery and learn more about linked streams in the user guide. Here is a quick summary of some of the more useful linked stream classes HoloViews currently offers and the parameters they supply:

PointerX/PointerY/PointerYX: The x,y or (x,y) position of the cursor.
SingleTap/DoubleTap/Tap: Position of single, double or all tap events.
BoundsX/BoundsY/BoundsXY: The x,y or x and y extents selected with the Bokeh box select tool.
RangeX/RangeY/RangeXY: The x,y or x and y range of the currently displayed axes
Selection1D: The selected glyphs as a 1D selection.

Any of these values can easily be tied to any visible element of your visualization.
A more advanced example
Let's now build a more advanced example using the eclipse dataset we explored earlier, where the stream supplies values when a particular Bokeh tool ("Box Select") is active:
End of explanation
"""


