Search code examples
pythonbokehscatter-plotmarker

How can I rotate the arrow marker in a Bokeh plot to show wind speed direction?


What I am trying to do is to use a scatter plot to display wind speed and its direction. I wanted to use the triangle marker and use the rotation to indicate wind speed. However, since bokeh default triangle is an equilateral triangle, the wind direction actually gets confusing (you can't tell which is the head which is the tail).

Is there any way I can change/create my own triangle that is Isosceles instead? this is so that I can tell which is head which is tail in the triangle.

here is a sample of what I have: Bokeh Scatter Plot with Rectangular Marker

Just to be clear here is a quick sketch of what shape marker I am looking for: Desired Rectangle vs Default Rectangle Marker

Here is a sample dataset. I have also used the arrow as suggested by J'e. Here is a sample result:

Sample Dataset Sample Result

This works ok but is not perfect. Because I plan to embed this plot and stretch it (sizing_mode="stretch_both"), the angles will look off. I don't think this was a problem when using rectangles and angles to draw.


Solution

  • There doesn't seem to be anything in the API that will do this directly. Using bokeh.models VeeHead, you can draw a VeeHead arrow

    import numpy as np
    from bokeh.plotting import figure, show, output_file
    from bokeh.models import ColumnDataSource
    from bokeh.models import Arrow, VeeHead
    
    # Create a sin wave of x/y coordinates 
    N = 300
    x = np.linspace(0, 4*np.pi, N)
    y = np.sin(x)
    
    source = ColumnDataSource(data=dict(x=x, y=y))
    TOOLS = "pan,wheel_zoom,box_zoom,reset,save"
    
    # create a new plot and add a renderer
    p = figure(tools=TOOLS, width=700, height=700, title=None)
    p.line('x', 'y', source=source)
    
    
    for i in range(N-1):
        p.add_layout(Arrow(end=VeeHead(size=25,fill_alpha=0.5), x_start=x[i], y_start=y[i], x_end=x[i+1], y_end=y[i+1])) 
    show(p)
    

    VeeHead no Tail

    Rather than having virtually no tail like in the first example, you you add one like in this example. Note that the tail has it's own style which I didn't set in this example.

    import numpy as np
    from bokeh.plotting import figure, show, output_file
    from bokeh.models import ColumnDataSource
    from bokeh.models import Arrow, VeeHead
    
    # prepare some date
    N = 300
    x = np.linspace(0, 4*np.pi, N)
    y = np.sin(x)
    
    source = ColumnDataSource(data=dict(x=x, y=y))
    TOOLS = "pan,wheel_zoom,box_zoom,reset,save"
    
    # create a new plot and add a renderer
    p = figure(tools=TOOLS, width=700, height=700, title=None)
    p.line('x', 'y', source=source)
    
    for i in range(11, N-1):
        m = (y[i]-y[i-1])/(x[i]-x[i-1])
        Y = m*(x[i]-x[i-10]) # not point slope
        p.add_layout(Arrow(end=VeeHead(size=25,fill_alpha=0.5), x_start=x[i-10], y_start=Y, x_end=x[i], y_end=y[i])) 
    show(p)
    

    enter image description here