Beispiel #1
0
def plot_daily_timeline(data, **kwargs):
    import itertools
    from datetime import timedelta, datetime, date
    from bokeh.core.enums import Dimensions, StepMode
    from bokeh.transform import dodge, cumsum
    from bokeh.plotting import figure
    from bokeh.models import ColumnDataSource, OpenURL, TapTool
    from bokeh.models import ResetTool, BoxZoomTool, HoverTool, PanTool, SaveTool
    from bokeh.models import NumeralTickFormatter, PrintfTickFormatter, Circle
    from bokeh.models.ranges import Range1d
    from bokeh import palettes, layouts

    from datetime import date
    x_range = kwargs.get('x_range', None)
    if not x_range:
        x_range = (date(2020, 2, 1) - timedelta(days=1),
                   date.today() + timedelta(days=1))

    y_range = 0, 60 * 24  # Minutes of a day

    data_source = ColumnDataSource(data)

    fig = figure(plot_height=500,
                 plot_width=1000,
                 title=f"Cluster timeline",
                 x_axis_type='datetime',
                 y_axis_type='linear',
                 x_range=x_range,
                 y_range=y_range,
                 tools="")

    fig.rect(y='minutes',
             x='date',
             width=timedelta(days=1) / 2,
             height=1,
             color='color',
             source=data_source)

    from datetime import time

    def index_to_time(index: int):
        hours = index // 60
        minutes = index % 60
        if index == 60 * 24:
            return time(0, 0)
        return time(hours, minutes)

    minutes = list(range(0, 60 * 24 + 1, 3 * 60))
    fig.yaxis.ticker = minutes
    fig.yaxis.major_label_overrides = {
        m: str(index_to_time(m))
        for m in minutes
    }

    fig.output_backend = "webgl"
    fig.toolbar.logo = None

    hover_tool = HoverTool(tooltips=[('Date', '@date{%F}')],
                           formatters={'date': 'datetime'},
                           mode='vline')

    fig.add_tools(hover_tool)
    fig.add_tools(SaveTool())
    fig.add_tools(BoxZoomTool())
    fig.add_tools(PanTool())
    fig.add_tools(ResetTool())
    return fig
SAME_CLUB_COLOR, DIFFERENT_CLUB_COLOR = "black", "red"
edge_attrs = {}

for start_node, end_node, _ in G.edges(data=True):
    edge_color = SAME_CLUB_COLOR if G.nodes[start_node]["club"] == G.nodes[
        end_node]["club"] else DIFFERENT_CLUB_COLOR
    edge_attrs[(start_node, end_node)] = edge_color

nx.set_edge_attributes(G, edge_attrs, "edge_color")

# Show with Bokeh
plot = Plot(plot_width=400,
            plot_height=400,
            x_range=Range1d(-1.1, 1.1),
            y_range=Range1d(-1.1, 1.1))
plot.title.text = "Graph Interaction Demonstration"

node_hover_tool = HoverTool(tooltips=[("index", "@index"), ("club", "@club")])
plot.add_tools(node_hover_tool, BoxZoomTool(), ResetTool())

graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0))

graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0])
graph_renderer.edge_renderer.glyph = MultiLine(line_color="edge_color",
                                               line_alpha=0.8,
                                               line_width=1)
plot.renderers.append(graph_renderer)

output_file("interactive_graphs.html")
show(plot)
Beispiel #3
0
    df_deep = df[df['depths'] >= 300]
    eq_info_shallow = ColumnDataSource(df_shallow)
    eq_info_middle = ColumnDataSource(df_middle)
    eq_info_deep = ColumnDataSource(df_deep)
    return (eq_info_shallow, eq_info_middle, eq_info_deep)


## range bounds supplied in web mercator coordinates
x_range, y_range = ((-18706892.5544, 21289852.6142), (-7631472.9040,
                                                      12797393.0236))

p = figure(width=1280, height=720, x_range=x_range, y_range=y_range)

## Styling
p.add_tile(tile_provider)
p.tools = [PanTool(), WheelZoomTool(), BoxZoomTool(), ResetTool(), SaveTool()]
# hover = HoverTool(tooltips=[("Magnitude","@magnitudes"), ("depths","@depths")])
hover = HoverTool(tooltips="""
     <div>
            <div>
                <span style="font-size: 12px; font-weight: bold;">@event_text</span>
            </div>
            <div>
                <span style="font-size: 10px; color: #696;">Event time: @event_time</span><br>
                <span style="font-size: 10px; color: #696;">Coordinates: (@longitudes,@latitudes)</span><br>
                <span style="font-size: 10px; color: #696;">Magnitude: @magnitudes @magnitude_type</span><br>
                <span style="font-size: 10px; color: #696;">Depth: @depths km</span>
            </div>
        </div>
""")
p.add_tools(hover)
                                  options=ListLignes,
                                  width=200)
# Bouton pour faire l'update
button_update = Button(label="Changer les dates", width=100)

# GRAPHE DES DONNEES

# cree un graphe scatter avec tout les elements repartis par heure et jour de la semaine
DAYS = ['Dim', 'Sam', 'Ven', 'Jeu', 'Mer', 'Mar', 'Lun']
hover = HoverTool(tooltips=[("Jour", "@Jour"), (
    "Ligne",
    "@NumLigne"), ("Véhicule",
                   "@NumParc"), ("KiKo", "@KiKo"), ("Vitesse", "@SpeedVA_AF")])
TOOLS = [
    PanTool(),
    BoxZoomTool(),
    LassoSelectTool(),
    BoxSelectTool(),
    WheelZoomTool(),
    ResetTool(),
    SaveTool(), hover
]
Jitter = figure(plot_width=560,
                plot_height=370,
                y_range=DAYS,
                x_axis_type='datetime',
                tools=TOOLS,
                output_backend="webgl")

Jitter.xaxis[0].formatter.days = ['%Hh']
Jitter.x_range.range_padding = 0
Beispiel #5
0
    def make_graph_plot(self):
        """
        Builds the graph portion of the final model from the DAG constructed
        by make_graph.
        """
        nodes = nx.nx_pydot.graphviz_layout(self._graph, prog="dot")
        node_x, node_y = zip(*nodes.values())
        models = [self._graph.nodes[x]["model"] for x in nodes]
        node_id = list(nodes.keys())
        node_source = ColumnDataSource({
            "x": node_x,
            "y": node_y,
            "index": node_id,
            "model": models
        })
        edge_x_coords = []
        edge_y_coords = []
        for start_node, end_node in self._graph.edges:
            edge_x_coords.extend([[nodes[start_node][0], nodes[end_node][0]]])
            edge_y_coords.extend([[nodes[start_node][1], nodes[end_node][1]]])
        edge_source = ColumnDataSource({
            "xs": edge_x_coords,
            "ys": edge_y_coords
        })

        p2 = Plot(outline_line_alpha=0.0)
        xinterval = max(max(node_x) - min(node_x), 200)
        yinterval = max(max(node_y) - min(node_y), 200)
        p2.x_range = Range1d(start=min(node_x) - 0.15 * xinterval,
                             end=max(node_x) + 0.15 * xinterval)
        p2.y_range = Range1d(start=min(node_y) - 0.15 * yinterval,
                             end=max(node_y) + 0.15 * yinterval)

        node_renderer = GlyphRenderer(
            data_source=node_source,
            glyph=Circle(x="x", y="y", size=15, fill_color="lightblue"),
            nonselection_glyph=Circle(x="x",
                                      y="y",
                                      size=15,
                                      fill_color="lightblue"),
            selection_glyph=Circle(x="x", y="y", size=15, fill_color="green"),
        )

        edge_renderer = GlyphRenderer(data_source=edge_source,
                                      glyph=MultiLine(xs="xs", ys="ys"))

        node_hover_tool = HoverTool(tooltips=[("id",
                                               "@index"), ("model", "@model")])
        node_hover_tool.renderers = [node_renderer]

        tap_tool = TapTool()
        tap_tool.renderers = [node_renderer]

        labels = LabelSet(
            x="x",
            y="y",
            text="model",
            source=node_source,
            text_font_size="8pt",
            x_offset=-20,
            y_offset=7,
        )

        help = Label(
            x=20,
            y=20,
            x_units="screen",
            y_units="screen",
            text_font_size="8pt",
            text_font_style="italic",
            text="Click on a model to see its attributes",
        )
        p2.add_layout(help)
        p2.add_layout(edge_renderer)
        p2.add_layout(node_renderer)
        p2.tools.extend(
            [node_hover_tool, tap_tool,
             BoxZoomTool(),
             ResetTool(),
             PanTool()])
        p2.renderers.append(labels)
        self._node_source = node_source
        self._edge_source = edge_source
        return p2
waveplot = figure(plot_width=TSPLOTW,
                  plot_height=TSPLOTH,
                  tools=TSTOOLS,
                  x_axis_type='datetime')
waveclr = 'black'
offset = 0
for s in st:
    waveplot.line(s.times('timestamp'),
                  s.data / max(s.data) + offset,
                  line_width=2,
                  color=waveclr)
    offset += 2
waveplot.circle('ontimes', 'y', source=source_triggers, size=10, color='red')
#waveplot.circle('offtimes','y', source=source_triggers, size=10, color='blue')
waveplot.add_tools(BoxZoomTool(dimensions="width"))

# STA/LTA functions
cftplot = figure(plot_width=TSPLOTW,
                 plot_height=TSPLOTH,
                 tools=TSTOOLS,
                 x_axis_type='datetime')
cftline = cftplot.line('times',
                       'cft',
                       source=source_stalta,
                       line_width=2,
                       color='black')
cftplot.add_tools(BoxZoomTool(dimensions="width"))
cftplot.x_range = waveplot.x_range
sta_on = Span(location=None,
              dimension='width',
Beispiel #7
0
print(sys.argv)
ghfam=sys.argv[1]
pkltreename=sys.argv[2]
#with open('st.pkl','rb') as f:
treefpath=os.path.join(os.environ['SCIENCEDIR'],'GHSeqs',ghfam.upper(),pkltreename)
dbfpath=os.path.join(os.environ['SCIENCEDIR'],'GHSeqs',ghfam.upper(),f'{ghfam.upper()}DB.sql')
ncfpath=os.path.join(os.environ['SCIENCEDIR'],'GHSeqs',ghfam.upper(),f'{ghfam.lower()}ds.nc')
print(treefpath)
with open(treefpath,'rb') as f:
    etetree=pickle.load(f)

hover_tool = HoverTool(names=['leaf_node'],tooltips=[    ("GB acc", "@gbacc"),('sf','@subfam'),\
                        ('phylum','@phylum'),('class','@class'),('species','@species')])
hover_tool2 = HoverTool(names=['metadata'],tooltips=[    ("GB acc", "@gbacc"),("ECs", "@ecs"),('PDBs','@pdbids')])
pheight=1400
p1 = figure(plot_width=850,plot_height=pheight,tools=[hover_tool,ResetTool(),BoxZoomTool(),PanTool()])#plot_width=1100, plot_height=700,
    #pheight=int(len(ptree.get_leaves())*1.5)
p2 = figure(plot_width=60,plot_height=pheight,tools=[hover_tool2],x_range=Range1d(0,2),y_range=p1.y_range)
ptree=phylotree.PhyloTree(etetree)
ptree.update_leafcdsdict_fromdb(dbfpath)
ptree.update_leafcdsdict_fromxr(ncfpath)
ptree.bokehdraw(plot=p1,rotation=0)

fglyph=MultiLine(xs='frame_xs',ys='frame_ys',line_width='frame_lws')
p1.add_glyph(ptree.leaf_cds,fglyph)#,name='leaf_node')
p1.add_glyph(ptree.internal_cds,fglyph)#,name='internal_node')
qglyph=p1.quad(left='nodebox_lefts',right='nodebox_rights',bottom='nodebox_bottoms',top='nodebox_tops',fill_color='qcolor',line_alpha=0,\
                        fill_alpha='qfillalpha',hatch_pattern='qhatch',source=ptree.leaf_cds,name='leaf_node')#,fill_alpha=0,line_alpha=0)                
#qglyph=Quad(left='nodebox_lefts',right='nodebox_rights',bottom='nodebox_bottoms',top='nodebox_tops',fill_color='qcolor',line_alpha=0,\
#                        hatch_pattern='qhatch')#,fill_alpha=0,line_alpha=0)                
#p1.add_glyph(ptree.leaf_cds,qglyph,name='leaf_node')#'leaf_node')#self.ntype)
Beispiel #8
0
                                    bldgType=[], bldgSubtype=[], 
                                    city=[], zipcode=[], state=[], gsf=[], score=[]))

hover = HoverTool(tooltips=[
    ("Name", "@facilityName"),
    ("Owner", "@owner"),
    ("Score", "@score"),
    ("Type", "@bldgSubtype"),
    ("City", "@city"),
    ("State", "@state"),
    ("Zip", "@zipcode"),
    ("GSF", "@gsf"+" sq ft"),      
])

p = figure(plot_height=800, plot_width=700, title="", toolbar_location="above", 
           tools=[hover,BoxZoomTool(),ResetTool()])
p.circle(x="x", y="y", source=source, size="dotSize", color="dotColor", 
         line_color="lineColor", line_width="lineWidth", fill_alpha="alpha")
p.axis.axis_label_text_font_style = "bold"
p.axis.major_label_text_font_style = "bold"
p.axis.axis_label_text_font_size = "16pt"
p.axis.major_label_text_font_size = "14pt"
p.xaxis.formatter = NumeralTickFormatter(format="0")
p.yaxis.formatter = NumeralTickFormatter(format="0")
p.title.text_font_size = "16pt"


# 6 different line colors, 3 different line widths, 3 different line dashes
#line_color
#line_width
#line_dash
Beispiel #9
0
def wsgraph(user_node, nodes_connect, prob):
    WS = nx.watts_strogatz_graph(int(user_node), int(nodes_connect),
                                 float(prob))
    degrees = WS.degree()
    nodes = WS.nodes()

    if user_node <= 10 and nodes_connect <= 10:
        multisize = 2
    elif user_node <= 20 and nodes_connect <= 20:
        multisize = 1.5
    elif user_node <= 30 and nodes_connect <= 30:
        multisize = 1
    elif user_node <= 50 and nodes_connect <= 50:
        multisize = .5
    else:
        multisize = .25

    eigenv = nx.eigenvector_centrality(WS, weight=None)
    between = nx.betweenness_centrality(WS, k=user_node, weight=None)
    edges = list(nx.edges(WS))
    edge_end = [edge[1] for edge in edges]

    node_color = {k: v for k, v in WS.degree()}
    node_size = {k: multisize * v for k, v in WS.degree()}
    nx.set_node_attributes(WS, node_color, 'node_color')
    nx.set_node_attributes(WS, node_size, 'node_size')
    mapper = LinearColorMapper(palette=Purples9, low=user_node, high=0)
    connections = list(node_color.values())
    eigenv = list(eigenv.values())
    between = list(between.values())

    source = ColumnDataSource(
        pd.DataFrame.from_dict({k: v
                                for k, v in WS.nodes(data=True)},
                               orient='index'))
    source.add(connections, "connections")
    source.add(eigenv, "eigenv")
    source.add(between, "between")

    boxcolor = BoxAnnotation(fill_color='white', line_color='black')

    plot_option = {
        'background_fill_color':
        'gray',
        'background_fill_alpha':
        .5,
        'tools': [
            PanTool(),
            WheelZoomTool(),
            TapTool(),
            BoxZoomTool(overlay=boxcolor),
            BoxSelectTool(overlay=boxcolor),
            ResetTool()
        ],
        'x_range':
        Range1d(-2, 2),
        'y_range':
        Range1d(-2, 2),
    }

    plot = figure(**plot_option, x_axis_location=None, y_axis_location=None)
    plot.title.text = "Watts-Strogatz Graph"
    graph = from_networkx(WS, nx.circular_layout)
    graph.node_renderer.data_source = source
    graph.node_renderer.glyph = Circle(name="Node",
                                       size='node_size',
                                       fill_color={
                                           'field': 'node_color',
                                           'transform': mapper
                                       })
    graph.edge_renderer.selection_glyph = MultiLine(line_color='white',
                                                    line_width=3)
    plot.add_tools(
        HoverTool(name="Node",
                  tooltips=[
                      ('Node', '$index'),
                      ('Connections', '@connections'),
                      ('Eigen Value', '@eigenv{.000000}'),
                      ('Betweenness', '@between{.000000}'),
                  ]))
    graph.selection_policy = NodesAndLinkedEdges()
    plot.toolbar.active_scroll = plot.select_one(WheelZoomTool)
    plot.toolbar.autohide = True

    plot.renderers.append(graph)
    return plot
def plotWeightsAndTrips():

    weights_y = None

    # Weights and dates
    df_weights = pd.read_sql_query(
        "SELECT * FROM weights ORDER BY weight_date",
        app.config['SQLALCHEMY_DATABASE_URI'])
    weightDates_x = df_weights['weight_date']
    weights_y = df_weights['weight']
    line_color = "black"
    line_width = 4

    df_solo_trips = pd.read_sql_query(
        "select * from trips where passenger_companion = '' ",
        app.config['SQLALCHEMY_DATABASE_URI'])

    df_companion_trips = pd.read_sql_query(
        "select * from trips where passenger_companion != '' ",
        app.config['SQLALCHEMY_DATABASE_URI'])

    if (df_solo_trips.empty == True and df_companion_trips.empty == True
            and df_weights.empty == True):
        return ["", "", ""]

    # Make sure we have a default bar height
    # in case there is no Weight data to show
    weights_y = weights_y if weights_y is not None else [0, 9]
    highestWeight_y = int(sorted(weights_y)[-1])
    lowestWeight_y = int(sorted(weights_y)[0])

    # tripsBarHeight = height of the vertical bar
    #                   which depends on max height of weights_y
    #                   + 10%
    # tripsBarStart = left start of the bar
    # tripsBarEnd = right end of the bar
    # tripsBarColors = global if one "color",
    #                   individual if an array
    #                   ["color_1", "color_2" [,...]]

    # Solo trips horizontal bars data
    tripsBarHeight = (highestWeight_y - lowestWeight_y
                      ) + (highestWeight_y - lowestWeight_y) * 0.1
    tripsBarStart = df_solo_trips['departure_date']
    tripsBarEnd = df_solo_trips['return_date']
    tripsBarColors = "red"  # Or individual colors for each value ["Cyan", "red",...]

    # Group trips horizontal bars data
    tripsBarGroupStart = df_companion_trips['departure_date']
    tripsBarGroupEnd = df_companion_trips['return_date']
    tripsBarGroupColors = "blue"

    # designing the plot style and information
    hover = HoverTool(
        show_arrow=True,
        point_policy='follow_mouse',
        tooltips=[("Trip from", " @left{%F}"), ("to", " @right{%F}"),
                  ("Date", " @x{%F}"), ("Weight", " @y Kg")],
        formatters={
            # use 'datetime' formatter for 'date' fields
            # default 'numeral' formatter for other fields
            '@x': 'datetime',
            '@left': 'datetime',
            '@right': 'datetime'
        })
    box = BoxSelectTool()
    wheel = WheelZoomTool()
    panTool = PanTool()
    boxZoomTool = BoxZoomTool()
    resetTool = ResetTool()

    TOOLS = [hover, box, wheel, panTool, boxZoomTool, resetTool]

    _plot = figure(title="Personal data registered since June 2014.",
                   x_axis_label='Dates',
                   x_axis_type='datetime',
                   y_axis_label='Kilograms',
                   y_axis_type='auto',
                   plot_width=650,
                   plot_height=450,
                   sizing_mode='scale_both',
                   tools=TOOLS)

    _plot.hbar(name="red",
               y=highestWeight_y * 0.96,
               height=tripsBarHeight,
               left=tripsBarStart,
               right=tripsBarEnd,
               color=tripsBarColors,
               legend_label="Solo trips")

    _plot.hbar(name="blue",
               y=highestWeight_y * 0.96,
               height=tripsBarHeight,
               left=tripsBarGroupStart,
               right=tripsBarGroupEnd,
               color=tripsBarGroupColors,
               legend_label="Group trips")

    # add weights' line to the figure
    _plot.line(weightDates_x,
               weights_y,
               color=line_color,
               width=line_width,
               legend_label="Weights",
               line_width=5)

    # Technicalities to show the graph
    curdoc().add_root(_plot)

    cdn_javascript = CDN.js_files[0]
    bokehScriptComponent, bokehDivComponent = components(_plot)
    graph = [cdn_javascript, bokehScriptComponent, bokehDivComponent]

    return graph
def create_plots(model1, model3, model4, model5, live_data, city,
                 display_name):
    """
    Output: Bokeh plot

    Creates individual timeseries plot
    """
    if city != 'chicago':
        model1 = model1.query("city_{} == 1 and display_name_{} == 1".format(
            city, display_name))
        # model2 = model2.query("city_{} == 1 and display_name_{} == 1".format(city, display_name))
        model3 = model3.query("city_{} == 1 and display_name_{} == 1".format(
            city, display_name))
        model4 = model4.query("city_{} == 1 and display_name_{} == 1".format(
            city, display_name))
        model5 = model5.query("city_{} == 1 and display_name_{} == 1".format(
            city, display_name))
    else:
        model1 = model1.query(
            "city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1"
            .format(display_name))
        # model2 = model2.query("city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1".format(display_name))
        model3 = model3.query(
            "city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1"
            .format(display_name))
        model4 = model4.query(
            "city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1"
            .format(display_name))
        model5 = model5.query(
            "city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1"
            .format(display_name))
    cartype = display_name.lower()
    live_data = live_data.query("display_name == @cartype and city == @city")

    source1 = ColumnDataSource(data=dict(d=model1['date'].astype(str),
                                         h=model1['hour'],
                                         f=model1['y_forecast'],
                                         n=model1['name']))

    # source2 = ColumnDataSource(
    #     data=dict(
    #         d=model2['date'].astype(str),
    #         h=model2['hour'],
    #         f=model2['y_forecast'],
    #         n=model2['name']
    #     )
    # )

    source3 = ColumnDataSource(data=dict(d=model3['date'].astype(str),
                                         h=model3['hour'],
                                         f=model3['y_forecast'],
                                         n=model3['name']))

    source4 = ColumnDataSource(data=dict(d=model4['date'].astype(str),
                                         h=model4['hour'],
                                         f=model4['y_forecast'],
                                         n=model4['name']))

    source5 = ColumnDataSource(data=dict(d=model5['date'].astype(str),
                                         h=model5['hour'],
                                         f=model5['y_forecast'],
                                         n=model5['name']))

    source6 = ColumnDataSource(data=dict(d=live_data['date'].astype(str),
                                         h=live_data['hour'],
                                         f=live_data['avg_price_est'],
                                         n=live_data['name']))

    hover = HoverTool(tooltips=[("Model",
                                 "@n"), ("Date",
                                         "@d"), ("Hour",
                                                 "@h"), ("Average Price",
                                                         "@f")])
    change_city = {
        'denver': 'Denver',
        'ny': 'New York',
        'chicago': 'Chicago',
        'seattle': 'Seattle',
        'sf': 'San Francisco'
    }
    p = figure(title="Forecast of {} {} Prices - {} to {}".format(
        change_city[city], display_name, sys.argv[1], sys.argv[2]),
               plot_width=1000,
               plot_height=500,
               x_axis_type="datetime",
               tools=[
                   hover,
                   PanTool(),
                   BoxZoomTool(),
                   ResizeTool(),
                   WheelZoomTool(),
                   PreviewSaveTool(),
                   ResetTool()
               ],
               toolbar_location="left",
               title_text_font_size="20pt")

    p.line(model1['record_time'],
           model1['y_forecast'],
           line_color='blue',
           line_width=2,
           legend="Random Forest Regressor",
           alpha=0.5,
           source=source1)
    # p.line(model2['record_time'], model2['y_forecast'], line_color='green', line_width=2, legend="RF Model 2 - Without Surge Multiplier", alpha=0.5, source=source2) # line_dash=[4,4]
    p.line(model3['record_time'],
           model3['y_forecast'],
           line_color='magenta',
           line_width=2,
           legend="Ridge Regression",
           alpha=0.5,
           source=source3)  # line_dash=[4,4]
    p.line(model4['record_time'],
           model4['y_forecast'],
           line_color='gray',
           line_width=2,
           legend="ARIMA Model",
           alpha=0.5,
           source=source4)  # line_dash=[4,4]
    p.line(model5['record_time'],
           model5['y_forecast'],
           line_color='green',
           line_width=2,
           legend="XGB Regressor",
           alpha=0.5,
           source=source5)  # line_dash=[4,4]
    # p.xaxis.axis_label = 'Time'
    # p.xaxis.axis_label_text_font_size = "10pt"
    p.yaxis.axis_label = 'Average Price Estimate'
    p.yaxis.axis_label_text_font_size = "20pt"
    p.yaxis.axis_label_standoff = 15
    p.xgrid[0].ticker.desired_num_ticks = 20
    xformatter = DatetimeTickFormatter(formats=dict(hours=["%H"]))
    p.xaxis.formatter = xformatter
    p.legend.label_text_font_size = "10pt"

    # add a text renderer to out plot (no data yet)
    r = p.circle(x=live_data['record_time'],
                 y=live_data['avg_price_est'],
                 legend="True Average Prices",
                 source=source6,
                 color='red')
    ds = r.data_source
    return p, ds
Beispiel #12
0
def interactive_plot(network,
                     network_name,
                     layout_func='fruchterman_reingold'):
    plot = Plot(plot_width=800,
                plot_height=800,
                x_range=Range1d(-1.1, 1.1),
                y_range=Range1d(-1.1, 1.1))

    plot.title.text = network_name

    plot.add_tools(
        HoverTool(
            tooltips=[('bird',
                       '@index'), ("age",
                                   "@Age"), ("sex",
                                             "@Sex"), ("location",
                                                       '@Location')]),
        TapTool(), BoxSelectTool(), BoxZoomTool(), ResetTool(), PanTool(),
        WheelZoomTool())
    if layout_func == 'fruchterman_reingold':
        graph_renderer = graphs.from_networkx(network,
                                              nx.fruchterman_reingold_layout,
                                              scale=1,
                                              center=(0, 0))

    elif layout_func == 'spring':
        graph_renderer = graphs.from_networkx(network,
                                              nx.spring_layout,
                                              scale=1,
                                              center=(0, 0))

    elif layout_func == 'circular':
        graph_renderer = graphs.from_networkx(network,
                                              nx.circular_layout,
                                              scale=1,
                                              center=(0, 0))

    elif layout_func == 'kamada':
        graph_renderer = graphs.from_networkx(network,
                                              nx.kamada_kawai_layout,
                                              scale=1,
                                              center=(0, 0))

    elif layout_func == 'spectral':
        graph_renderer = graphs.from_networkx(network,
                                              nx.spectral_layout,
                                              scale=1,
                                              center=(0, 0))

    else:
        graph_renderer = graphs.from_networkx(network,
                                              nx.fruchterman_reingold_layout,
                                              scale=1,
                                              center=(0, 0))

    centrality = nx.algorithms.centrality.betweenness_centrality(network)
    """ first element are nodes again """
    _, nodes_centrality = zip(*centrality.items())
    max_centraliy = max(nodes_centrality)
    c_centrality = [7 + 15 * t / max_centraliy for t in nodes_centrality]

    import community  #python-louvain
    partition = community.best_partition(network)
    p_, nodes_community = zip(*partition.items())

    community_colors = [
        '#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33',
        '#a65628', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc',
        '#e5d8bd', '#fddaec', '#1b9e77', '#d95f02', '#7570b3', '#e7298a',
        '#66a61e', '#e6ab02', '#a6761d', '#666666'
    ]
    colors = [
        community_colors[t % len(community_colors)] for t in nodes_community
    ]

    _, Sex = zip(*nx.get_node_attributes(network, 'Sex').items())
    _, Age = zip(*nx.get_node_attributes(network, 'Age').items())
    _, Location = zip(*nx.get_node_attributes(network, 'Location').items())
    graph_renderer.node_renderer.data_source.add(c_centrality, 'centrality')
    graph_renderer.node_renderer.data_source.add(Sex, 'Sex')
    graph_renderer.node_renderer.data_source.add(Age, 'Age')
    graph_renderer.node_renderer.data_source.add(Location, 'Location')

    graph_renderer.node_renderer.data_source.add(colors, 'colors')
    graph_renderer.node_renderer.glyph = Circle(size='centrality',
                                                fill_color='colors')
    graph_renderer.node_renderer.selection_glyph = Circle(
        size='centrality', fill_color=Spectral4[2])
    graph_renderer.node_renderer.hover_glyph = Circle(size=15,
                                                      fill_color=Spectral4[1])

    _, edge_weights = zip(*nx.get_edge_attributes(network, 'weight').items())
    max_weights = max(edge_weights)
    c_weights = [7 + 2 * (t / max_weights) for t in edge_weights]

    graph_renderer.edge_renderer.data_source.add(c_weights, 'weight')
    graph_renderer.edge_renderer.glyph = MultiLine(line_color="#757474",
                                                   line_alpha=0.2,
                                                   line_width='weight')
    graph_renderer.edge_renderer.selection_glyph = MultiLine(
        line_color=Spectral4[2], line_width='weight')
    graph_renderer.edge_renderer.hover_glyph = MultiLine(
        line_color=Spectral4[1], line_width='weight')

    graph_renderer.selection_policy = graphs.NodesAndLinkedEdges()
    graph_inspection_policy = graphs.NodesOnly()
    #graph_renderer.inspection_policy = graphs.EdgesAndLinkedNodes()

    plot.renderers.append(graph_renderer)

    #output_file("interactive_graphs.html")
    return plot
def __make_daybyday_interactive_timeline(
    df: pd.DataFrame,
    *,
    geo_df: geopandas.GeoDataFrame,
    value_col: str,
    transform_df_func: Callable[[pd.DataFrame], pd.DataFrame] = None,
    stage: Union[DiseaseStage, Literal[Select.ALL]] = Select.ALL,
    count: Union[Counting, Literal[Select.ALL]] = Select.ALL,
    out_file_basename: str,
    subplot_title_prefix: str,
    plot_aspect_ratio: float = None,
    cmap=None,
    n_cbar_buckets: int = None,
    n_buckets_btwn_major_ticks: int = None,
    n_minor_ticks_btwn_major_ticks: int = None,
    per_capita_denominator: int = None,
    x_range: Tuple[float, float],
    y_range: Tuple[float, float],
    min_visible_y_range: float,
    should_make_video: bool,
) -> InfoForAutoload:
    """Create the bokeh interactive timeline plot(s)

    This function takes the given DataFrame, which must contain COVID data for locations
    on different dates, and a GeoDataFrame, which contains the long/lat coords for those
    locations, and creates an interactive choropleth of the COVID data over time.

    :param df: The COVID data DataFrame
    :type df: pd.DataFrame
    :param geo_df: The geometry GeoDataFrame for the locations in `df`
    :type geo_df: geopandas.GeoDataFrame
    :param value_col: The column of `df` containing the values to plot in the
    choropleth; should be something like "Case_Counts" or "Case_Diff_From_Prev_Day"
    :type value_col: str
    :param stage: The DiseaseStage to plot, defaults to Select.ALL. If ALL, then all
    stages are plotted and are stacked vertically.
    :type stage: Union[DiseaseStage, Literal[Select.ALL]], optional
    :param count: The Counting to plot, defaults to Select.ALL. If ALL, then all
    count types are plotted and are stacked horizontally.
    :type count: Union[Counting, Literal[Select.ALL]], optional
    :param out_file_basename: The basename of the file to save the interactive plots to
    (there are two components, the JS script and the HTML <div>)
    :type out_file_basename: str
    :param subplot_title_prefix: What the first part of the subplot title should be;
    probably a function of `value_col` (if value_col is "Case_Counts" then this param
    might be "Cases" or "# of Cases")
    :type subplot_title_prefix: str
    :param x_range: The range of the x-axis as (min, max)
    :type x_range: Tuple[float, float]
    :param y_range: The range of the y-axis as (min, max)
    :type y_range: Tuple[float, float]
    :param min_visible_y_range: The minimum height (in axis units) of the y-axis; it
    will not be possible to zoom in farther than this on the choropleth.
    :type min_visible_y_range: float
    :param should_make_video: Optionally run through the timeline day by day, capture
    a screenshot for each day, and then stitch the screenshots into a video. The video
    shows the same info as the interactive plots, but not interactively. This easily
    takes 20x as long as just making the graphs themselves, so use with caution.
    :type should_make_video: bool
    :param transform_df_func: This function expects data in a certain format, and does
    a bunch of preprocessing (expected to be common) before plotting. This gives you a
    chance to do any customization on the postprocessed df before it's plotted. Defaults
    to None, in which case no additional transformation is performed.
    :type transform_df_func: Callable[[pd.DataFrame], pd.DataFrame], optional
    :param plot_aspect_ratio: The aspect ratio of the plot as width/height; if set, the
    aspect ratio will be fixed to this. Defaults to None, in which case the aspect ratio
    is determined from the x_range and y_range arguments
    :type plot_aspect_ratio: float, optional
    :param cmap: The colormap to use as either a matplotlib-compatible colormap or a
    list of hex strings (e.g., ["#ae8f1c", ...]). Defaults to None in which case a
    reasonable default is used.
    :type cmap: Matplotlib-compatible colormap or List[str], optional
    :param n_cbar_buckets: How many colorbar buckets to use. Has little effect if the
    colormap is continuous, but always works in conjunction with
    n_buckets_btwn_major_ticks to determine the number of major ticks. Defaults to 6.
    :type n_cbar_buckets: int, optional
    :param n_buckets_btwn_major_ticks: How many buckets are to lie between colorbar
    major ticks, determining how many major ticks are drawn. Defaults to 1.
    :type n_buckets_btwn_major_ticks: int, optional
    :param n_minor_ticks_btwn_major_ticks: How many minor ticks to draw between colorbar
    major ticks. Defaults to 8 (which means each pair of major ticks has 10 ticks
    total).
    :type n_minor_ticks_btwn_major_ticks: int, optional
    :param per_capita_denominator: When describing per-capita numbers, what to use as
    the denominator (e.g., cases per 100,000 people). If None, it is automatically
    computed per plot to be appropriately scaled for the data.
    :type per_capita_denominator: int, optional
    :raises ValueError: [description]
    :return: The two pieces of info required to make a Bokeh autoloading HTML+JS plot:
    the HTML div to be inserted somewhere in the HTML body, and the JS file that will
    load the plot into that div.
    :rtype: InfoForAutoload
    """

    Counting.verify(count, allow_select=True)
    DiseaseStage.verify(stage, allow_select=True)

    # The date as a string, so that bokeh can use it as a column name
    STRING_DATE_COL = "String_Date_"
    # A column whose sole purpose is to be a (the same) date associated with each
    # location
    FAKE_DATE_COL = "Fake_Date_"
    # The column we'll actually use for the colors; it's computed from value_col
    COLOR_COL = "Color_"

    # Under no circumstances may you change this date format
    # It's not just a pretty date representation; it actually has to match up with the
    # date strings computed in JS
    DATE_FMT = r"%Y-%m-%d"

    ID_COLS = [
        REGION_NAME_COL,
        Columns.DATE,
        Columns.STAGE,
        Columns.COUNT_TYPE,
    ]

    if cmap is None:
        cmap = cmocean.cm.matter

    if n_cbar_buckets is None:
        n_cbar_buckets = 6

    if n_buckets_btwn_major_ticks is None:
        n_buckets_btwn_major_ticks = 1

    if n_minor_ticks_btwn_major_ticks is None:
        n_minor_ticks_btwn_major_ticks = 8

    n_cbar_major_ticks = n_cbar_buckets // n_buckets_btwn_major_ticks + 1

    try:
        color_list = [
            # Convert matplotlib colormap to bokeh (list of hex strings)
            # https://stackoverflow.com/a/49934218
            RGB(*rgb).to_hex()
            for i, rgb in enumerate((255 * cmap(range(256))).astype("int"))
        ]
    except TypeError:
        color_list = cmap

    color_list: List[BokehColor]

    if stage is Select.ALL:
        stage_list = list(DiseaseStage)
    else:
        stage_list = [stage]

    if count is Select.ALL:
        count_list = list(Counting)
    else:
        count_list = [count]

    stage_list: List[DiseaseStage]
    count_list: List[Counting]

    stage_count_list: List[Tuple[DiseaseStage, Counting]] = list(
        itertools.product(stage_list, count_list))

    df = df.copy()

    # Unadjust dates (see SaveFormats._adjust_dates)
    normalized_dates = df[Columns.DATE].dt.normalize()
    is_at_midnight = df[Columns.DATE] == normalized_dates
    df.loc[is_at_midnight, Columns.DATE] -= pd.Timedelta(days=1)
    df.loc[~is_at_midnight, Columns.DATE] = normalized_dates[~is_at_midnight]

    min_date, max_date = df[Columns.DATE].agg(["min", "max"])
    dates: List[pd.Timestamp] = pd.date_range(start=min_date,
                                              end=max_date,
                                              freq="D")
    max_date_str = max_date.strftime(DATE_FMT)

    # Get day-by-day case diffs per location, date, stage, count-type

    # Make sure data exists for every date for every state so that the entire country is
    # plotted each day; fill missing data with 0 (missing really *is* as good as 0)
    # enums will be replaced by their name (kind of important)
    id_cols_product: pd.MultiIndex = pd.MultiIndex.from_product(
        [
            df[REGION_NAME_COL].unique(),
            dates,
            [s.name for s in DiseaseStage],
            [c.name for c in Counting],
        ],
        names=ID_COLS,
    )

    df = (id_cols_product.to_frame(index=False).merge(
        df,
        how="left",
        on=ID_COLS,
    ).sort_values(ID_COLS))

    df[STRING_DATE_COL] = df[Columns.DATE].dt.strftime(DATE_FMT)
    df[Columns.CASE_COUNT] = df[Columns.CASE_COUNT].fillna(0)

    if transform_df_func is not None:
        df = transform_df_func(df)

    df = geo_df.merge(df, how="inner", on=REGION_NAME_COL)[[
        REGION_NAME_COL,
        Columns.DATE,
        STRING_DATE_COL,
        Columns.STAGE,
        Columns.COUNT_TYPE,
        value_col,
    ]]

    dates: List[pd.Timestamp] = [
        pd.Timestamp(d) for d in df[Columns.DATE].unique()
    ]

    values_mins_maxs = (df[df[value_col] > 0].groupby(
        [Columns.STAGE, Columns.COUNT_TYPE])[value_col].agg(["min", "max"]))

    vmins: pd.Series = values_mins_maxs["min"]
    vmaxs: pd.Series = values_mins_maxs["max"]

    pow10s_series: pd.Series = vmaxs.map(
        lambda x: int(10**(-np.floor(np.log10(x)))))

    # _pow_10s_series_dict = {}
    # for stage in DiseaseStage:
    #     _pow_10s_series_dict.update(
    #         {
    #             (stage.name, Counting.TOTAL_CASES.name): 100000,
    #             (stage.name, Counting.PER_CAPITA.name): 10000,
    #         }
    #     )

    # pow10s_series = pd.Series(_pow_10s_series_dict)

    vmins: dict = vmins.to_dict()
    vmaxs: dict = vmaxs.to_dict()

    for stage in DiseaseStage:
        _value_key = (stage.name, Counting.PER_CAPITA.name)
        if per_capita_denominator is None:
            _max_pow10 = pow10s_series.loc[(slice(None),
                                            Counting.PER_CAPITA.name)].max()
        else:
            _max_pow10 = per_capita_denominator

        vmins[_value_key] *= _max_pow10
        vmaxs[_value_key] *= _max_pow10
        pow10s_series[_value_key] = _max_pow10

    percap_pow10s: pd.Series = df.apply(
        lambda row: pow10s_series[
            (row[Columns.STAGE], row[Columns.COUNT_TYPE])],
        axis=1,
    )

    _per_cap_rows = df[Columns.COUNT_TYPE] == Counting.PER_CAPITA.name
    df.loc[_per_cap_rows, value_col] *= percap_pow10s.loc[_per_cap_rows]

    # Ideally we wouldn't have to pivot, and we could do a JIT join of state longs/lats
    # after filtering the data. Unfortunately this is not possible, and a long data
    # format leads to duplication of the very large long/lat lists; pivoting is how we
    # avoid that. (This seems to be one downside of bokeh when compared to plotly)
    df = (df.pivot_table(
        index=[REGION_NAME_COL, Columns.STAGE, Columns.COUNT_TYPE],
        columns=STRING_DATE_COL,
        values=value_col,
        aggfunc="first",
    ).reset_index().merge(
        geo_df[[REGION_NAME_COL, LONG_COL, LAT_COL]],
        how="inner",
        on=REGION_NAME_COL,
    ))

    # All three oclumns are just initial values; they'll change with the date slider
    df[value_col] = df[max_date_str]
    df[FAKE_DATE_COL] = max_date_str
    df[COLOR_COL] = np.where(df[value_col] > 0, df[value_col], "NaN")

    # Technically takes a df but we don't need the index
    bokeh_data_source = ColumnDataSource(
        {k: v.tolist()
         for k, v in df.to_dict(orient="series").items()})

    filters = [[
        GroupFilter(column_name=Columns.STAGE, group=stage.name),
        GroupFilter(column_name=Columns.COUNT_TYPE, group=count.name),
    ] for stage, count in stage_count_list]

    figures = []

    for subplot_index, (stage, count) in enumerate(stage_count_list):
        # fig = bplotting.figure()
        # ax: plt.Axes = fig.add_subplot(
        #     len(stage_list), len(count_list), subplot_index
        # )

        # # Add timestamp to top right axis
        # if subplot_index == 2:
        #     ax.text(
        #         1.25,  # Coords are arbitrary magic numbers
        #         1.23,
        #         f"Last updated {NOW_STR}",
        #         horizontalalignment="right",
        #         fontsize="small",
        #         transform=ax.transAxes,
        #     )

        view = CDSView(source=bokeh_data_source,
                       filters=filters[subplot_index])

        vmin = vmins[(stage.name, count.name)]
        vmax = vmaxs[(stage.name, count.name)]

        # Compute and set axes titles
        if stage is DiseaseStage.CONFIRMED:
            fig_stage_name = "Cases"
        elif stage is DiseaseStage.DEATH:
            fig_stage_name = "Deaths"
        else:
            raise ValueError

        fig_title_components: List[str] = []
        if subplot_title_prefix is not None:
            fig_title_components.append(subplot_title_prefix)

        fig_title_components.append(fig_stage_name)

        if count is Counting.PER_CAPITA:
            _per_cap_denom = pow10s_series[(stage.name, count.name)]
            fig_title_components.append(f"Per {_per_cap_denom:,d} people")
            formatter = PrintfTickFormatter(format=r"%2.3f")
            label_standoff = 12
            tooltip_fmt = "{0.000}"
        else:
            formatter = NumeralTickFormatter(format="0.0a")
            label_standoff = 10
            tooltip_fmt = "{0}"

        color_mapper = LogColorMapper(
            color_list,
            low=vmin,
            high=vmax,
            nan_color="#f2f2f2",
        )

        fig_title = " ".join(fig_title_components)

        if plot_aspect_ratio is None:
            if x_range is None or y_range is None:
                raise ValueError("Must provide both `x_range` and `y_range`" +
                                 " when `plot_aspect_ratio` is None")
            plot_aspect_ratio = (x_range[1] - x_range[0]) / (y_range[1] -
                                                             y_range[0])

        # Create figure object
        p = bplotting.figure(
            title=fig_title,
            title_location="above",
            tools=[
                HoverTool(
                    tooltips=[
                        ("Date", f"@{{{FAKE_DATE_COL}}}"),
                        ("State", f"@{{{REGION_NAME_COL}}}"),
                        ("Count", f"@{{{value_col}}}{tooltip_fmt}"),
                    ],
                    toggleable=False,
                ),
                PanTool(),
                BoxZoomTool(match_aspect=True),
                ZoomInTool(),
                ZoomOutTool(),
                ResetTool(),
            ],
            active_drag=None,
            aspect_ratio=plot_aspect_ratio,
            output_backend="webgl",
            lod_factor=4,
            lod_interval=400,
            lod_threshold=1000,
            lod_timeout=300,
        )

        p.xgrid.grid_line_color = None
        p.ygrid.grid_line_color = None
        # Finally, add the actual choropleth data we care about
        p.patches(
            LONG_COL,
            LAT_COL,
            source=bokeh_data_source,
            view=view,
            fill_color={
                "field": COLOR_COL,
                "transform": color_mapper
            },
            line_color="black",
            line_width=0.25,
            fill_alpha=1,
        )

        # Add evenly spaced ticks and their labels to the colorbar
        # First major, then minor
        # Adapted from https://stackoverflow.com/a/50314773
        bucket_size = (vmax / vmin)**(1 / n_cbar_buckets)
        tick_dist = bucket_size**n_buckets_btwn_major_ticks

        # Simple log scale math
        major_tick_locs = (
            vmin * (tick_dist**np.arange(0, n_cbar_major_ticks))
            # * (bucket_size ** 0.5) # Use this if centering ticks on buckets
        )
        # Get minor locs by linearly interpolating between major ticks
        minor_tick_locs = []
        for major_tick_index, this_major_tick in enumerate(
                major_tick_locs[:-1]):
            next_major_tick = major_tick_locs[major_tick_index + 1]

            # Get minor ticks as numbers in range [this_major_tick, next_major_tick]
            # and exclude the major ticks themselves (once we've used them to
            # compute the minor tick locs)
            minor_tick_locs.extend(
                np.linspace(
                    this_major_tick,
                    next_major_tick,
                    n_minor_ticks_btwn_major_ticks + 2,
                )[1:-1])

        color_bar = ColorBar(
            color_mapper=color_mapper,
            ticker=FixedTicker(ticks=major_tick_locs,
                               minor_ticks=minor_tick_locs),
            formatter=formatter,
            label_standoff=label_standoff,
            major_tick_out=0,
            major_tick_in=13,
            major_tick_line_color="white",
            major_tick_line_width=1,
            minor_tick_out=0,
            minor_tick_in=5,
            minor_tick_line_color="white",
            minor_tick_line_width=1,
            location=(0, 0),
            border_line_color=None,
            bar_line_color=None,
            orientation="vertical",
        )

        p.add_layout(color_bar, "right")
        p.hover.point_policy = "follow_mouse"

        # Bokeh axes (and most other things) are splattable
        p.axis.visible = False

        figures.append(p)

    # Make all figs pan and zoom together by setting their axes equal to each other
    # Also fix the plots' aspect ratios
    figs_iter = iter(np.ravel(figures))
    anchor_fig = next(figs_iter)

    if x_range is not None and y_range is not None:
        data_aspect_ratio = (x_range[1] - x_range[0]) / (y_range[1] -
                                                         y_range[0])
    else:
        data_aspect_ratio = plot_aspect_ratio

    if x_range is not None:
        anchor_fig.x_range = Range1d(
            *x_range,
            bounds="auto",
            min_interval=min_visible_y_range * data_aspect_ratio,
        )

    if y_range is not None:
        anchor_fig.y_range = Range1d(*y_range,
                                     bounds="auto",
                                     min_interval=min_visible_y_range)

    for fig in figs_iter:
        fig.x_range = anchor_fig.x_range
        fig.y_range = anchor_fig.y_range

    # 2x2 grid (for now)
    gp = gridplot(
        figures,
        ncols=len(count_list),
        sizing_mode="scale_both",
        toolbar_location="above",
    )
    plot_layout = [gp]

    # Ok, pause
    # Now we're going into a whole other thing: we're doing all the JS logic behind a
    # date slider that changes which date is shown on the graphs. The structure of the
    # data is one column per date, one row per location, and a few extra columns to
    # store the data the graph will use. When we adjust the date of the slider, we copy
    # the relevant column of the df into the columns the graphs are looking at.
    # That's the easy part; the hard part is handling the "play button" functionality,
    # whereby the user can click one button and the date slider will periodically
    # advance itself. That requires a fair bit of logic to schedule and cancel the
    # timers and make it all feel right.

    # Create unique ID for the JS playback info object for this plot (since it'll be on
    # the webpage with other plots, and their playback info isn't shared)
    _THIS_PLOT_ID = uuid.uuid4().hex

    __TIMER = "'timer'"
    __IS_ACTIVE = "'isActive'"
    __SELECTED_INDEX = "'selectedIndex'"
    __BASE_INTERVAL_MS = "'BASE_INTERVAL'"  # Time (in MS) btwn frames when speed==1
    __TIMER_START_DATE = "'startDate'"
    __TIMER_ELAPSED_TIME_MS = "'elapsedTimeMS'"
    __TIMER_ELAPSED_TIME_PROPORTION = "'elapsedTimeProportion'"
    __SPEEDS_KEY = "'SPEEDS'"
    __PLAYBACK_INFO = f"window._playbackInfo_{_THIS_PLOT_ID}"

    _PBI_TIMER = f"{__PLAYBACK_INFO}[{__TIMER}]"
    _PBI_IS_ACTIVE = f"{__PLAYBACK_INFO}[{__IS_ACTIVE}]"
    _PBI_SELECTED_INDEX = f"{__PLAYBACK_INFO}[{__SELECTED_INDEX}]"
    _PBI_TIMER_START_DATE = f"{__PLAYBACK_INFO}[{__TIMER_START_DATE}]"
    _PBI_TIMER_ELAPSED_TIME_MS = f"{__PLAYBACK_INFO}[{__TIMER_ELAPSED_TIME_MS}]"
    _PBI_TIMER_ELAPSED_TIME_PROPORTION = (
        f"{__PLAYBACK_INFO}[{__TIMER_ELAPSED_TIME_PROPORTION}]")
    _PBI_BASE_INTERVAL = f"{__PLAYBACK_INFO}[{__BASE_INTERVAL_MS}]"
    _PBI_SPEEDS = f"{__PLAYBACK_INFO}[{__SPEEDS_KEY}]"
    _PBI_CURR_INTERVAL_MS = (
        f"{_PBI_BASE_INTERVAL} / {_PBI_SPEEDS}[{_PBI_SELECTED_INDEX}]")

    _SPEED_OPTIONS = [0.25, 0.5, 1.0, 2.0]
    _DEFAULT_SPEED = 1.0
    _DEFAULT_SELECTED_INDEX = _SPEED_OPTIONS.index(_DEFAULT_SPEED)

    _SETUP_WINDOW_PLAYBACK_INFO = f"""
        if (typeof({__PLAYBACK_INFO}) === 'undefined') {{
            {__PLAYBACK_INFO} = {{
                {__TIMER}: null,
                {__IS_ACTIVE}: false,
                {__SELECTED_INDEX}: {_DEFAULT_SELECTED_INDEX},
                {__TIMER_START_DATE}: null,
                {__TIMER_ELAPSED_TIME_MS}: 0,
                {__TIMER_ELAPSED_TIME_PROPORTION}: 0,
                {__BASE_INTERVAL_MS}: 1000,
                {__SPEEDS_KEY}: {_SPEED_OPTIONS}
            }};
        }}

    """

    _DEFFUN_INCR_DATE = f"""
        // See this link for why this works (it's an undocumented feature?)
        // https://discourse.bokeh.org/t/5254
        // Tl;dr we need this to automatically update the hover as the play button plays
        // Without this, the hover tooltip only updates when we jiggle the mouse
        // slightly

        let prev_val = null;
        source.inspect.connect(v => prev_val = v);

        function updateDate() {{
            {_PBI_TIMER_START_DATE} = new Date();
            {_PBI_TIMER_ELAPSED_TIME_MS} = 0
            if (dateSlider.value < maxDate) {{
                dateSlider.value += 86400000;
            }}

            if (dateSlider.value >= maxDate) {{
                console.log(dateSlider.value, maxDate)
                console.log('reached end')
                clearInterval({_PBI_TIMER});
                {_PBI_IS_ACTIVE} = false;
                playPauseButton.active = false;
                playPauseButton.change.emit();
                playPauseButton.label = 'Restart';
            }}

            dateSlider.change.emit();

            // This is pt. 2 of the prev_val/inspect stuff above
            if (prev_val !== null) {{
                source.inspect.emit(prev_val);
            }}
        }}
    """

    _DO_START_TIMER = f"""
        function startLoopTimer() {{
            updateDate();
            if ({_PBI_IS_ACTIVE}) {{
                {_PBI_TIMER} = setInterval(updateDate, {_PBI_CURR_INTERVAL_MS})
            }}

        }}

        {_PBI_TIMER_START_DATE} = new Date();

        // Should never be <0 or >1 but I am being very defensive here
        const proportionRemaining = 1 - (
            {_PBI_TIMER_ELAPSED_TIME_PROPORTION} <= 0
            ? 0
            : {_PBI_TIMER_ELAPSED_TIME_PROPORTION} >= 1
            ? 1
            : {_PBI_TIMER_ELAPSED_TIME_PROPORTION}
        );
        const remainingTimeMS = (
            {_PBI_CURR_INTERVAL_MS} * proportionRemaining
        );
        const initialInterval = (
            {_PBI_TIMER_ELAPSED_TIME_MS} === 0
            ? 0
            : remainingTimeMS
        );

        {_PBI_TIMER} = setTimeout(
            startLoopTimer,
            initialInterval
        );
    """

    _DO_STOP_TIMER = f"""
        const now = new Date();
        {_PBI_TIMER_ELAPSED_TIME_MS} += (
            now.getTime() - {_PBI_TIMER_START_DATE}.getTime()
        );
        {_PBI_TIMER_ELAPSED_TIME_PROPORTION} = (
            {_PBI_TIMER_ELAPSED_TIME_MS} / {_PBI_CURR_INTERVAL_MS}
        );
        clearInterval({_PBI_TIMER});
    """

    update_on_date_change_callback = CustomJS(
        args={"source": bokeh_data_source},
        code=f"""

        {_SETUP_WINDOW_PLAYBACK_INFO}

        const sliderValue = cb_obj.value;
        const sliderDate = new Date(sliderValue)
        // Ugh, actually requiring the date to be YYYY-MM-DD (matching DATE_FMT)
        const dateStr = sliderDate.toISOString().split('T')[0]

        const data = source.data;

        {_PBI_TIMER_ELAPSED_TIME_MS} = 0

        if (typeof(data[dateStr]) !== 'undefined') {{
            data['{value_col}'] = data[dateStr]

            const valueCol = data['{value_col}'];
            const colorCol = data['{COLOR_COL}'];
            const fakeDateCol = data['{FAKE_DATE_COL}']

            for (var i = 0; i < data['{value_col}'].length; i++) {{
                const value = valueCol[i]
                if (value == 0) {{
                    colorCol[i] = 'NaN';
                }} else {{
                    colorCol[i] = value;
                }}

                fakeDateCol[i] = dateStr;
            }}

            source.change.emit();

        }}

        """,
    )

    # Taking day-over-day diffs means the min slider day is one more than the min data
    # date (might be off by 1 if not using day over diffs but in practice not an issue)
    min_slider_date = min_date + pd.Timedelta(days=1)
    date_slider = DateSlider(
        start=min_slider_date,
        end=max_date,
        value=max_date,
        step=1,
        sizing_mode="stretch_width",
        width_policy="fit",
    )
    date_slider.js_on_change("value", update_on_date_change_callback)

    play_pause_button = Toggle(
        label="Start playing",
        button_type="success",
        active=False,
        sizing_mode="stretch_width",
    )

    animate_playback_callback = CustomJS(
        args={
            "source": bokeh_data_source,
            "dateSlider": date_slider,
            "playPauseButton": play_pause_button,
            "maxDate": max_date,
            "minDate": min_slider_date,
        },
        code=f"""

        {_SETUP_WINDOW_PLAYBACK_INFO}
        {_DEFFUN_INCR_DATE}

        if (dateSlider.value >= maxDate) {{
            if (playPauseButton.active) {{
                dateSlider.value = minDate;
                dateSlider.change.emit();

                // Hack to get timer to wait after date slider wraps; any positive
                // number works but the smaller the better
                {_PBI_TIMER_ELAPSED_TIME_MS} = 1;
            }}
        }}

        const active = cb_obj.active;
        {_PBI_IS_ACTIVE} = active;

        if (active) {{
            playPauseButton.label = 'Playing – Click/tap to pause'
            {_DO_START_TIMER}
        }} else {{
            playPauseButton.label = 'Paused – Click/tap to play'
            {_DO_STOP_TIMER}
        }}

        """,
    )

    play_pause_button.js_on_click(animate_playback_callback)

    change_playback_speed_callback = CustomJS(
        args={
            "source": bokeh_data_source,
            "dateSlider": date_slider,
            "playPauseButton": play_pause_button,
            "maxDate": max_date,
        },
        code=f"""

        {_SETUP_WINDOW_PLAYBACK_INFO}
        {_DEFFUN_INCR_DATE}

        // Must stop timer before handling changing the speed, as stopping the timer
        // saves values based on the current (unchaged) speed selection
        if ({_PBI_TIMER} !== null) {{
            {_DO_STOP_TIMER}
        }}

        const selectedIndex = cb_obj.active;
        {_PBI_SELECTED_INDEX} = selectedIndex;

        if ({_PBI_IS_ACTIVE}) {{
            {_DO_START_TIMER}
        }} else {{
            {_PBI_TIMER_ELAPSED_TIME_MS} = 0
        }}

        console.log({__PLAYBACK_INFO})

    """,
    )

    playback_speed_radio = RadioButtonGroup(
        labels=[f"{speed:.2g}x speed" for speed in _SPEED_OPTIONS],
        active=_DEFAULT_SELECTED_INDEX,
        sizing_mode="stretch_width",
    )
    playback_speed_radio.js_on_click(change_playback_speed_callback)

    plot_layout.append(
        layout_column(
            [
                date_slider,
                layout_row(
                    [play_pause_button, playback_speed_radio],
                    height_policy="min",
                ),
            ],
            width_policy="fit",
            height_policy="min",
        ))
    plot_layout = layout_column(plot_layout, sizing_mode="scale_both")

    # grid = gridplot(figures, ncols=len(count_list), sizing_mode="stretch_both")

    # Create the autoloading bokeh plot info (HTML + JS)
    js_path = str(Path(out_file_basename + "_autoload").with_suffix(".js"))
    tag_html_path = str(
        Path(out_file_basename + "_div_tag").with_suffix(".html"))

    js_code, tag_code = autoload_static(plot_layout, CDN, js_path)
    tag_uuid = re.search(r'id="([^"]+)"', tag_code).group(1)
    tag_code = re.sub(r'src="([^"]+)"', f'src="\\1?uuid={tag_uuid}"', tag_code)

    with open(Paths.DOCS / js_path,
              "w") as f_js, open(Paths.DOCS / tag_html_path, "w") as f_html:
        f_js.write(js_code)
        f_html.write(tag_code)

    # Create the video by creating stills of the graphs for each date and then stitching
    # the images into a video
    if should_make_video:
        save_dir: Path = PNG_SAVE_ROOT_DIR / out_file_basename
        save_dir.mkdir(parents=True, exist_ok=True)

        STILL_WIDTH = 1500
        STILL_HEIGHT = int(np.ceil(STILL_WIDTH / plot_aspect_ratio) *
                           1.05)  # Unclear why *1.05 is necessary
        gp.height = STILL_HEIGHT
        gp.width = STILL_WIDTH
        gp.sizing_mode = "fixed"
        orig_title = anchor_fig.title.text

        for date in dates:
            date_str = date.strftime(DATE_FMT)
            anchor_fig.title = Title(text=f"{orig_title} {date_str}")

            for p in figures:
                p.title = Title(text=p.title.text, text_font_size="20px")

            # Just a reimplementation of the JS code in the date slider's callback
            data = bokeh_data_source.data
            data[value_col] = data[date_str]

            for i, value in enumerate(data[value_col]):
                if value == 0:
                    data[COLOR_COL][i] = "NaN"
                else:
                    data[COLOR_COL][i] = value

                data[FAKE_DATE_COL][i] = date_str

            save_path: Path = (save_dir / date_str).with_suffix(".png")
            export_png(gp, filename=save_path)
            resize_to_even_dims(save_path, pad_bottom=0.08)

            if date == max(dates):
                poster_path: Path = (
                    PNG_SAVE_ROOT_DIR /
                    (out_file_basename + "_poster")).with_suffix(".png")
                poster_path.write_bytes(save_path.read_bytes())

        make_video(save_dir, out_file_basename, 0.9)

    print(f"Did interactive {out_file_basename}")

    return (js_code, tag_code)
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.models import HoverTool, ColumnDataSource, TapTool, ResetTool, BoxZoomTool
from BokehStructureGraph.BokehStructureGraph import BokehStructureGraph
import numpy as np

x=np.linspace(-10,10,100)
y=np.sin(x)
signal=y+np.random.normal(0,.3,size=x.shape[0])
source=ColumnDataSource({'x':x,'y':y,'signal':signal})
# draw the structure graph of a basic figure model
f=figure()
f.scatter(x='x',y='signal',source=source,  color='red', legend_label="measured")
f.line(x='x',y='y',source=source, color='blue', legend_label="truth")
f.xgrid.grid_line_color='white'
f.ygrid.grid_line_color='white'
f.background_fill_color="#eeeeee"
f.title.text = "Figure being analyzed"
f.legend.location = "top_left"
f.legend.click_policy="hide"
f.legend.background_fill_alpha = 0.0
T = HoverTool()
T.renderers=[f.renderers[0]]
S = TapTool()
S.renderers=[f.renderers[0]]
f.tools=[T,S, ResetTool(),BoxZoomTool()]
show(row(f,BokehStructureGraph(f).model))
Beispiel #15
0
def fb_graph():

    with open('facebook.json', 'r') as read_file:
        data = json.load(read_file)
    FB = nx.Graph(incoming_graph_data=data)
    degrees = FB.degree()
    nodes = FB.nodes()

    eigenv = nx.eigenvector_centrality(FB, weight=None)
    between = nx.betweenness_centrality(FB, weight=None)

    node_color = {k: v for k, v in FB.degree()}
    node_size = {k: .1 * v for k, v in FB.degree()}
    nx.set_node_attributes(FB, node_color, 'node_color')
    nx.set_node_attributes(FB, node_size, 'node_size')
    mapper = LinearColorMapper(palette=Viridis256, low=256, high=0)
    connections = list(node_color.values())
    eigenv = list(eigenv.values())
    between = list(between.values())

    source = ColumnDataSource(
        pd.DataFrame.from_dict({k: v
                                for k, v in FB.nodes(data=True)},
                               orient='index'))
    source.add(connections, "connections")
    source.add(eigenv, "eigenv")
    source.add(between, "between")

    boxcolor = BoxAnnotation(fill_color='white', line_color='black')

    plot_option = {
        'background_fill_color':
        'gray',
        'background_fill_alpha':
        .5,
        'tools': [
            PanTool(),
            WheelZoomTool(),
            TapTool(),
            BoxZoomTool(overlay=boxcolor),
            BoxSelectTool(overlay=boxcolor),
            ResetTool()
        ],
        'x_range':
        Range1d(-1, 1),
        'y_range':
        Range1d(-1, 1),
        'plot_width':
        800,
        'plot_height':
        800,
    }

    plot = figure(**plot_option,
                  x_axis_location=None,
                  y_axis_location=None,
                  output_backend="webgl")
    plot.title.text = "Facebook Graph"
    graph = from_networkx(FB, nx.spring_layout)
    graph.node_renderer.data_source = source
    graph.node_renderer.glyph = Circle(name="Node",
                                       size='node_size',
                                       fill_color={
                                           'field': 'node_color',
                                           'transform': mapper
                                       })
    graph.edge_renderer.glyph = MultiLine(line_width=.5)
    graph.edge_renderer.selection_glyph = MultiLine(line_color='white',
                                                    line_width=1)
    plot.add_tools(
        HoverTool(name="Node",
                  tooltips=[
                      ('Node', '$index'),
                      ('Connections', '@connections'),
                      ('Eigen Value', '@eigenv{.000000}'),
                      ('Betweenness', '@between{.000000}'),
                  ]))
    graph.selection_policy = NodesAndLinkedEdges()
    plot.toolbar.active_scroll = plot.select_one(WheelZoomTool)

    #4038 total nodes

    plot.renderers.append(graph)
    script, div = components(plot)
    cdn_js = CDN.js_files
    cdn_css = CDN.css_files

    return plot
Beispiel #16
0
def plot_clusters_bokeh(list_x,
                        list_y,
                        list_pais,
                        k,
                        etiquetas,
                        grados_pertenencia,
                        title='Title',
                        to_save=True):

    ### Plotear con hover tool

    ### Colores a usar para cada cluster
    colores = ['blue', 'yellow', 'red', 'green', 'orange', 'purple']

    ### Creo la herramienta de hover tool
    hover = HoverTool(tooltips=[
        ("pais", "@pais"),
        ("index", "$index"),
        ("(x,y)", "(@x, @y)"),
        ("cluster_id", "@cluster_id"),
        ("Pertenencia_clusters", "@grados_p"),
    ])

    ### Creo la figura
    p = figure(
        plot_width=700,
        plot_height=500,
        tools=[hover,
               PanTool(),
               ResetTool(),
               BoxZoomTool(),
               WheelZoomTool()],
        title=title)

    ### PLoteo cada cluster
    for i in range(k):
        source = ColumnDataSource(
            data={
                'x': list_x[np.where(etiquetas == i)],
                'y': list_y[np.where(etiquetas == i)],
                'pais': list_pais[np.where(etiquetas == i)],
                'grados_p': grados_pertenencia[np.where(etiquetas == i)],
                'cluster_id': etiquetas[np.where(etiquetas == i)]
            })
        p.circle('x', 'y', size=12, fill_color=colores[i], source=source)

    ### Ploteo centroides
    #p.square(centroids_pca[:,0], centroids_pca[:,1], size=15,    fill_color='black')

    ### Labels (componentes principales)
    p.xaxis.axis_label = 'Componente principal 1'
    p.yaxis.axis_label = 'Componente principal 2'
    #p.xaxis.axis_label = datos.columns[-2]
    #p.yaxis.axis_label = datos.columns[-1]

    if to_save:
        ### Guardo el resultado
        output_file('outputs/ClustersGenerados/cluster_inicial_' + title +
                    '.html')
        save(p)

    return None
Beispiel #17
0
def remove_cells_by_hand(z,
                         imgs,
                         cell_poss,
                         cell_sizes,
                         cell_acts,
                         size=(600, 600),
                         vmax=None,
                         remove_border=0):
    from bokeh.models import LassoSelectTool, BoxSelectTool, ResetTool, WheelZoomTool, BoxZoomTool
    from bokeh.plotting import figure, show, output_notebook
    from bokeh.models import CustomJS, ColumnDataSource, Circle, Toolbar, ToolbarPanel
    from bokeh.io import push_notebook, reset_output
    from bokeh.layouts import row
    output_notebook(hide_banner=True)

    img = imgs[z]
    if vmax != None: img = np.clip(img, 0, vmax)
    nx, ny = img.shape

    # delete border Cells
    border_idxs = np.asarray([
        idx for idx, pos in enumerate(cell_poss[z])
        if pos[0] <= remove_border or pos[0] >= nx - remove_border
        or pos[1] <= remove_border or pos[1] >= ny - remove_border
    ])
    cell_poss[z] = np.delete(cell_poss[z], border_idxs, axis=0)
    cell_sizes[z] = np.delete(cell_sizes[z], border_idxs, axis=0)
    cell_acts[z] = np.delete(cell_acts[z], border_idxs, axis=0)
    cell_pos = cell_poss[z]
    cell_size = cell_sizes[z]
    cell_act = cell_acts[z]

    def save(b):
        global y
        print(y)
        if y == '': return
        rem_idxs = np.asarray(
            [idx for idx, y in enumerate(map(float, y.split(','))) if y < 0])
        y = ''
        print(cell_poss[z].shape)
        cell_poss[z] = np.delete(cell_poss[z], rem_idxs, axis=0)
        cell_sizes[z] = np.delete(cell_sizes[z], rem_idxs, axis=0)
        cell_acts[z] = np.delete(cell_acts[z], rem_idxs, axis=0)
        print(cell_poss[z].shape)

    button = widgets.Button(description="Save",
                            button_style='success',
                            icon='save')
    button.on_click(save)
    display(button)

    source = ColumnDataSource(data=dict(x=[], y=[], s=[]))
    select_callback = CustomJS(args=dict(s=source),
                               code="""
        var idxs = s.selected.indices;
        if (idxs.length == 0) return;
        var d = s.data;
        for (var i = 0; i < idxs.length; i++) d['y'][idxs[i]] = -1000;
        s.change.emit();
        cmd = "pf.y = '" + d['y'] + "'";
        IPython.notebook.kernel.execute(cmd, {}, {});
        """)
    lasso_tool = LassoSelectTool(callback=select_callback,
                                 select_every_mousemove=False)
    box_tool = BoxSelectTool(callback=select_callback,
                             select_every_mousemove=False)
    wheel_tool = WheelZoomTool(zoom_on_axis=False)
    tools = [ResetTool(), BoxZoomTool(), lasso_tool, box_tool, wheel_tool]

    p = figure(title="draw to remove:",
               output_backend="webgl",
               toolbar_location="above",
               tools=tools,
               x_range=(0, nx),
               y_range=(ny, 0),
               plot_width=size[0],
               plot_height=size[1])
    p.xgrid.visible = p.ygrid.visible = False
    p.toolbar_location = None
    p.add_layout(ToolbarPanel(toolbar=Toolbar(tools=tools)), "right")

    lasso_overlay = p.select_one(LassoSelectTool).overlay
    lasso_overlay.fill_color = "firebrick"
    lasso_overlay.line_color = "white"

    h = show(row(p), notebook_handle=True)

    p.image(image=[img[::-1]],
            x=0,
            y=ny,
            dw=nx,
            dh=ny,
            palette="Viridis256",
            level="image")
    scat = p.circle('x',
                    'y',
                    color='red',
                    size='s',
                    source=source,
                    fill_color='red',
                    alpha=1)
    scat.data_source.data = dict(x=cell_pos[:, 0],
                                 y=cell_pos[:, 1],
                                 s=cell_size * 2)
    scat.nonselection_glyph = Circle(line_color='red', fill_color='red')

    push_notebook(handle=h)
Beispiel #18
0
def plot_cluster_bokeh_cambios(X_data_pca,
                               list_x,
                               list_y,
                               list_xv,
                               list_yv,
                               k,
                               cambios_variables,
                               list_pais,
                               grados_pertenencia,
                               etiquetas,
                               etiquetas_prev,
                               centroids,
                               centroids_viej,
                               centroids_pca,
                               title='Title',
                               to_save=True):

    ### Hover tool para los datos
    hover = HoverTool(tooltips=[
        ("pais", "@pais"),
        ("index", "$index"),
        ("(x,y)", "(@x, @y)"),
        ("(Cambio_x,Cambio_y)", "(@xv, @yv)"),
        ("cluster_id", "@cluster_id"),
        ("Pertenencia_clusters", "@grados_p"),
    ])

    ### Colores a usar para cada cluster
    colores = ['blue', 'yellow', 'red', 'green', 'orange', 'purple']

    ### Crear la figura
    p = figure(
        plot_width=700,
        plot_height=500,
        tools=[hover,
               PanTool(),
               ResetTool(),
               BoxZoomTool(),
               WheelZoomTool()],
        title=title)

    ### PLoteo cada conjunto
    for i in range(k):
        source = ColumnDataSource(
            data={
                'x': list_x[np.where(etiquetas == i)],
                'y': list_y[np.where(etiquetas == i)],
                'xv': list_xv[np.where(etiquetas == i)],
                'yv': list_yv[np.where(etiquetas == i)],
                'pais': list_pais[np.where(etiquetas == i)],
                'grados_p': grados_pertenencia[np.where(etiquetas == i)],
                'cluster_id': etiquetas[np.where(etiquetas == i)]
            })
        p.circle('x', 'y', size=12, fill_color=colores[i], source=source)

    ### Veo cuales cambiaron de cluster
    etiquetas_cambios = np.where(etiquetas_prev - etiquetas != 0)
    etiqs = etiquetas[etiquetas_cambios]

    ### PLoteo los elementos de cada conjunto que cambiaron de cluster

    ### Listas para los elementos que cambiaron de cluster
    X_data_cambios = X_data_pca[etiquetas_cambios]
    list_x = X_data_cambios[:, 0]
    list_y = X_data_cambios[:, 1]
    list_xv = cambios_variables[:, 0]
    list_yv = cambios_variables[:, 1]
    list_pais = list_pais[etiquetas_cambios]
    grados_pertenencia = grados_pertenencia[etiquetas_cambios]

    ## Plotear elementos que cambiaron de cluster
    for i in range(k):
        source = ColumnDataSource(
            data={
                'x': list_x[np.where(etiqs == i)],
                'y': list_y[np.where(etiqs == i)],
                'xv': list_xv[np.where(etiqs == i)],
                'yv': list_yv[np.where(etiqs == i)],
                'pais': list_pais[np.where(etiqs == i)],
                'grados_p': grados_pertenencia[np.where(etiqs == i)],
                'cluster_id': etiqs[np.where(etiqs == i)]
            })
        p.square('x', 'y', size=6, fill_color='white', source=source)

    ### Ploteo centroides

    ### Listas para centroiodes
    cambios_centroids = centroids_viej - centroids
    list_x = centroids_pca[:, 0]
    list_y = centroids_pca[:, 1]
    list_xv = cambios_centroids[:, 0]
    list_yv = cambios_centroids[:, 1]

    # Plotar centroids
    source = ColumnDataSource(data={
        'x': list_x,
        'y': list_y,
        'xv': list_xv,
        'yv': list_yv
    })
    #    p.square('x','y', size=15,             fill_color='black',source=source)

    ### Labels de la grafica (componentes principales)
    p.xaxis.axis_label = 'Componente principal 1'
    p.yaxis.axis_label = 'Componente principal 2'
    #p.xaxis.axis_label = datos.columns[-2]
    #p.yaxis.axis_label = datos.columns[-1]

    if to_save:
        ### Guardar resultados
        output_file('outputs/ClustersGenerados/cluster' + title + '.html')
        save(p)

    return None
Beispiel #19
0
    def __init__(self, n_rectangles=1000, clear_interval=20000, **kwargs):
        """
        kwargs are applied to the bokeh.models.plots.Plot constructor
        """
        self.n_rectangles = n_rectangles
        self.clear_interval = clear_interval
        self.last = 0

        self.source = ColumnDataSource(
            data=dict(start=[time() - clear_interval],
                      duration=[0.1],
                      key=['start'],
                      name=['start'],
                      color=['white'],
                      duration_text=['100 ms'],
                      worker=['foo'],
                      y=[0],
                      worker_thread=[1],
                      alpha=[0.0]))

        x_range = DataRange1d(range_padding=0)
        y_range = DataRange1d(range_padding=0)

        self.root = figure(title="Task Stream",
                           id='bk-task-stream-plot',
                           x_range=x_range,
                           y_range=y_range,
                           toolbar_location="above",
                           x_axis_type='datetime',
                           min_border_right=35,
                           tools='',
                           **kwargs)
        self.root.yaxis.axis_label = 'Worker Core'

        rect = self.root.rect(source=self.source,
                              x="start",
                              y="y",
                              width="duration",
                              height=0.4,
                              fill_color="color",
                              line_color="color",
                              line_alpha=0.6,
                              fill_alpha="alpha",
                              line_width=3)
        rect.nonselection_glyph = None

        self.root.yaxis.major_label_text_alpha = 0
        self.root.yaxis.minor_tick_line_alpha = 0
        self.root.xgrid.visible = False

        hover = HoverTool(point_policy="follow_mouse",
                          tooltips="""
                <div>
                    <span style="font-size: 12px; font-weight: bold;">@name:</span>&nbsp;
                    <span style="font-size: 10px; font-family: Monaco, monospace;">@duration_text</span>
                </div>
                """)

        tap = TapTool(callback=OpenURL(url='/profile?key=@name'))

        self.root.add_tools(hover, tap, BoxZoomTool(),
                            ResetTool(reset_size=False),
                            PanTool(dimensions="width"),
                            WheelZoomTool(dimensions="width"))
        if ExportTool:
            export = ExportTool()
            export.register_plot(self.root)
            self.root.add_tools(export)

        # Required for update callback
        self.task_stream_index = [0]
Beispiel #20
0
mapper = CategoricalColorMapper(factors=factors, palette=colors)
from bokeh.transform import jitter

p.circle(x='scaled_suicides_no',
         y=jitter('year', width=0.6, range=p.y_range),
         source=source,
         alpha=0.3)

p.x_range.range_padding = 0
p.ygrid.grid_line_color = None

#p.circle('suicides_no', 'age', size=4, source=source,
#    legend='sex', fill_alpha=0.2, color = {"field":"sex","transform":mapper})
p.add_tools(PanTool())
p.add_tools(WheelZoomTool())
p.add_tools(BoxZoomTool())
p.add_tools(ResetTool())
show(p)
#https://gist.github.com/dela3499/e159b388258b5f1a7a3bac42fc0179fd
'''from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models import HoverTool, CategoricalColorMapper
from bokeh.io import output_notebook

output_notebook()

sourcef = ColumnDataSource(
        data=wh[(wh.country=='Japan')&(wh.sex=='female')]
    )
sourcem = ColumnDataSource(
        data=wh[(wh.country=='Japan')&(wh.sex=='male')]
    )
Beispiel #21
0
           tools=TOOLS,
           plot_width=1000,
           toolbar_location="left",
           webgl=True)

p.y_range = DataRange1d()

p.xaxis.major_label_orientation = pi / 4
p.x_range.follow = "end"
p.x_range.range_padding = 0

p.grid.grid_line_alpha = 0.3
p.min_border_left = 10
p.min_border_top = 10
p.add_tools(CrosshairTool(line_color='green', line_alpha=0.4))
p.add_tools(BoxZoomTool(dimensions=["width"]))
p.add_tools(WheelZoomTool(dimensions=["width"]))

#차트 데이터 모양 설정
width = 12 * 60 * 60 * 1000  # half day in ms
p.segment(x0='date',
          y0='low',
          x1='date',
          y1='high',
          color="color",
          source=source)

#p.segment(x0='date', y0='open',  x1='date', y1='close',line_width=15, color="color", source=source)
p.rect('date',
       'mid',
       width,
Beispiel #22
0
    def add_graph(self, field_names, legends, window='hann', window_length=256, noverlap=128):
        """ add a spectrogram plot to the graph

        field_names: can be a list of fields from the data set, or a list of
        functions with the data set as argument and returning a tuple of
        (field_name, data)
        legends: description for the field_names that will appear in the title of the plot
        window: the type of window to use for the frequency analysis. check scipy documentation for available window types.
        window_length: length of the analysis window in samples.
        noverlap: number of overlapping samples between windows.
        """

        if self._had_error: return
        try:
            data_set = {}

            timestamp_key = 'timestamp'

            if 'timestamp_sample' in self._cur_dataset.data.keys():
                timestamp_key = 'timestamp_sample'

            data_set[timestamp_key] = self._cur_dataset.data[timestamp_key]

            # calculate the sampling frequency
            # (Note: logging dropouts are not taken into account here)
            delta_t = ((data_set[timestamp_key][-1] - data_set[timestamp_key][0]) * 1.0e-6) / len(data_set[timestamp_key])
            if delta_t < 0.000001: # avoid division by zero
                self._had_error = True
                return

            sampling_frequency = int(1.0 / delta_t)

            if sampling_frequency < 100: # require min sampling freq
                self._had_error = True
                return

            field_names_expanded = self._expand_field_names(field_names, data_set)

            # calculate the spectrogram
            psd = dict()
            for key in field_names_expanded:
                frequency, time, psd[key] = scipy.signal.spectrogram(
                    data_set[key], fs=sampling_frequency, window=window,
                    nperseg=window_length, noverlap=noverlap, scaling='density')

            # sum all psd's
            key_it = iter(psd)
            sum_psd = psd[next(key_it)]
            for key in key_it:
                sum_psd += psd[key]

            # offset = int(((1024/2.0)/250.0)*1e6)
            # scale time to microseconds and add start time as offset
            time = time * 1.0e6 + self._cur_dataset.data[timestamp_key][0]

            image = [10 * np.log10(sum_psd)]
            title = self.title
            for legend in legends:
                title += " " + legend
            title += " [dB]"

            # assume maximal data points per pixel at full resolution
            max_num_data_points = 2.0*self._config['plot_width']
            if len(time) > max_num_data_points:
                step_size = int(len(time) / max_num_data_points)
                time = time[::step_size]
                image[0] = image[0][:, ::step_size]

            color_mapper = LinearColorMapper(palette="Viridis256", low=np.amin(image), high=np.amax(image))

            self._p.y_range = Range1d(frequency[0], frequency[-1])
            self._p.toolbar_location = 'above'
            self._p.image(image=image, x=time[0], y=frequency[0], dw=(time[-1]-time[0]),
                          dh=(frequency[-1]-frequency[0]), color_mapper=color_mapper)
            color_bar = ColorBar(color_mapper=color_mapper,
                                 major_label_text_font_size="5pt",
                                 ticker=BasicTicker(desired_num_ticks=5),
                                 formatter=PrintfTickFormatter(format="%f"),
                                 title='[dB]',
                                 label_standoff=6, border_line_color=None, location=(0, 0))
            self._p.add_layout(color_bar, 'right')

            # add plot zoom tool that only zooms in time axis
            wheel_zoom = WheelZoomTool()
            self._p.toolbar.tools = [PanTool(), wheel_zoom, BoxZoomTool(), ResetTool(), SaveTool()]   # updated_tools
            self._p.toolbar.active_scroll = wheel_zoom

        except (KeyError, IndexError, ValueError, ZeroDivisionError) as error:
            print(type(error), "(" + self._data_name + "):", error)
            self._had_error = True
Beispiel #23
0
                                               Simulation_End))
print('PercentError is {}'.format(PercentError))

#%%--------------------------MODEL COMPARISON-----------------------------------------

#This code is only run when comparing the model results to field measurements. It is typically used for model validation
if Compare_To_MeasuredData == 1:
    #Calculate the electricity consumed over the simulation and the % error for validation purposes
    Model['Total Electricity Consumption (kWh)'] = Model[
        'Electricity Consumed (kWh)'].cumsum()
    Model['Cumulative Percent Error (%)'] = (
        Model['Total Electricity Consumption (kWh)'] -
        Model['Power_EnergySum_kWh']) / (Model['Power_EnergySum_kWh'] +
                                         0.000000000001) * 100

    tools = [LassoSelectTool(), WheelZoomTool(), BoxZoomTool(), ResetTool()]

    #The rest of the code creates and saves plots comparing the HPWH model to the monitored data. Detailed comments will not be provided.
    p1 = figure(width=1200,
                height=600,
                x_axis_label='Time (hr)',
                y_axis_label='Electric Power (W)',
                tools=tools)
    p1.title.text_font_size = '12pt'
    p1.line(x=Model['Time (hr)'],
            y=Model['Electric Power (W)'],
            legend='Model',
            color='red')
    p1.circle(x=Model['Time (hr)'],
              y=Model['Power_PowerSum_W'],
              legend='Data',
relevantWords, relevant_tweet = get_tweet_word2vec(default_search_1,
                                                   tweet_dataset)
rel_tweet = relevant_tweet.copy(deep=True)

dates, counts = get_trend(relevant_tweet)

########### Create Visualizations ##################

# Line graph for trend
plot_trend = figure(title='Trend of Tweets',
                    plot_width=600,
                    plot_height=200,
                    x_axis_type="datetime",
                    tools=[HoverTool(),
                           ResetTool(),
                           BoxZoomTool()])
line1 = plot_trend.line(x=dates, y=counts, line_width=2)
line1_datasource = line1.data_source
plot_trend.xaxis.axis_label = "Date"
plot_trend.yaxis.axis_label = "Relevant Tweet Count"

# Widgets - Search, button
button_go = Button(label="Search Revelant Tweets", button_type="success")
button_go.on_click(bt_compare_click)

rad_text = PreText(text="Choose Word Embedding :")
radio_group = RadioGroup(labels=["WordNet Synset", "Word2Vec"],
                         inline=True,
                         active=1)
user_text = PreText(text="")
                                        ealpha=ealpha))

# plot
################
# Start timer
t0 = time.time()
################

x_range = Range1d(0, 1)
y_range = Range1d(0, 1)
plot = Plot(x_range=x_range,
            y_range=y_range,
            plot_width=plot_width,
            plot_height=plot_height,
            title=title)
plot.add_tools(PanTool(), WheelZoomTool(), BoxZoomTool(), PreviewSaveTool(),
               ResetTool())

# edges
seg = Segment(x0="ex0", y0="ey0", x1="ex1", y1="ey1", \
        line_width="ewidth",)
seg_glyph = plot.add_glyph(esource, seg)

# circles
circ = Circle(x="x", y="y", size='size', line_color=None)
circ_glyph = plot.add_glyph(nsource, circ)

################
# End Timer
print(bokeh.__version__)
print("Time to plot with Bokeh:", time.time() - t0, "seconds")
Beispiel #26
0
def plot_image(filename, save=False):

    hdu = fits.open(filename)
    dheader = dict(hdu[0].header)
    for k in dheader:
        if len(k) >= 2:
            print(f"{k}: {dheader[k]}")

    print(hdu.info())
    data = hdu[0].data

    # quick hot pixel/ cosmic ray mask
    mask, cdata = detect_cosmics(
        data, psfmodel='gauss',
        psffwhm=4, psfsize=2*round(4)+1, # just a guess
        sepmed=False, sigclip = 4.25,
        niter=3, objlim=10, cleantype='idw', verbose=False
    )

    # show how many pixels are saturated
    SATURATION = 2**(hdu[0].header['bitpix'])
    mmask = cdata >= SATURATION*0.9
    labels, ngroups = label(mmask)
    print('Saturated Areas:',ngroups)
    labeli, counts = np.unique(labels, return_counts=True)
    bad_pix = {'x':[], 'y':[], 'value':[]}
    # loop through each group to find position
    for i in range(1,labeli[-1]+1):
        imask = labels == i
        yc,xc = np.argwhere(imask).mean(0)
        bad_pix['x'].append(xc)
        bad_pix['y'].append(yc)
        bad_pix['value'].append(cdata[imask].mean())

    pprint(bad_pix)

    # create a figure with text on mouse hover\
    print("Saturated pixels are marked with red. These are pixels which have exceeded the maximum value for brightness, and are thus not suitable for use as comparison stars.")
    fig = figure(tooltips=[("x", "$x"), ("y", "$y"), ("value", "@image")], plot_width=800, plot_height=800,
        tools=[PanTool(),BoxZoomTool(),WheelZoomTool(),ResetTool(),HoverTool()])
    fig.x_range.range_padding = fig.y_range.range_padding = 0

    r = fig.multi_line('x', 'y', source={'x':[],'y':[]},color='white',line_width=3)
    fig.add_tools(FreehandDrawTool(renderers=[r]))

    ##TODO: add colorbar

    # set up a colobar + data range
    color_mapper = LogColorMapper(palette="Cividis256", low=np.percentile(data, 55), high=np.percentile(data, 99))

    # must give a vector of image data for image parameter
    fig.image(
        image=[cdata],
          x=0, y=0, dw=hdu[0].data.shape[1], dh=hdu[0].data.shape[0],
          level="image", color_mapper=color_mapper
    )

    # plot saturated stars
    fig.x(bad_pix['x'], bad_pix['y'], size=25, color='red', line_width=3)
    fig.x(bad_pix['x'], bad_pix['y'], size=25, color='white', line_width=1)
    # TODO figure out hover value

    fig.grid.grid_line_width = 0.5

    color_bar = ColorBar(color_mapper=color_mapper, ticker=LogTicker(),
                         label_standoff=12, border_line_color=None, location=(0,0))

    fig.add_layout(color_bar, 'right')

    if save:
        output_file("interactivefits.html")
    else:
        show(fig)
Beispiel #27
0
def large_plot(n: int) -> Tuple[Model, Set[Model]]:
    from bokeh.models import (
        BoxSelectTool,
        BoxZoomTool,
        Column,
        ColumnDataSource,
        DataRange1d,
        GlyphRenderer,
        Grid,
        Line,
        LinearAxis,
        PanTool,
        Plot,
        ResetTool,
        SaveTool,
        WheelZoomTool,
        ZoomInTool,
        ZoomOutTool,
    )

    col = Column()
    objects: Set[Model] = {col}

    for i in range(n):
        source = ColumnDataSource(data=dict(x=[0, i + 1], y=[0, i + 1]))
        xdr = DataRange1d()
        ydr = DataRange1d()
        plot = Plot(x_range=xdr, y_range=ydr)
        xaxis = LinearAxis()
        plot.add_layout(xaxis, "below")
        yaxis = LinearAxis()
        plot.add_layout(yaxis, "left")
        xgrid = Grid(dimension=0)
        plot.add_layout(xgrid, "center")
        ygrid = Grid(dimension=1)
        plot.add_layout(ygrid, "center")
        tickers = [
            xaxis.ticker, xaxis.formatter, yaxis.ticker, yaxis.formatter
        ]
        glyph = Line(x='x', y='y')
        renderer = GlyphRenderer(data_source=source, glyph=glyph)
        plot.renderers.append(renderer)
        pan = PanTool()
        zoom_in = ZoomInTool()
        zoom_out = ZoomOutTool()
        wheel_zoom = WheelZoomTool()
        box_zoom = BoxZoomTool()
        box_select = BoxSelectTool()
        save = SaveTool()
        reset = ResetTool()
        tools = [
            pan, zoom_in, zoom_out, wheel_zoom, box_zoom, box_select, save,
            reset
        ]
        plot.add_tools(*tools)
        col.children.append(plot)
        objects |= set([
            xdr,
            ydr,
            xaxis,
            xaxis.major_label_policy,
            yaxis,
            yaxis.major_label_policy,
            xgrid,
            ygrid,
            renderer,
            renderer.view,
            glyph,
            source,
            source.selected,
            source.selection_policy,
            plot,
            plot.x_scale,
            plot.y_scale,
            plot.toolbar,
            plot.title,
            box_zoom.overlay,
            box_select.overlay,
        ] + tickers + tools)

    return col, objects
def mn_display(edges_df, analysis_id):
    # create networkx object
    G = nx.from_pandas_edgelist(edges_df,
                                source='cluster1',
                                target='cluster2',
                                edge_attr=True)

    # convert node attributes
    node_dict = dict()
    node_dict['index'] = list(G.nodes())

    node_attr_keys = [
        attr_key for node in list(G.nodes(data=True))
        for attr_key in node[1].keys()
    ]
    node_attr_keys = list(set(node_attr_keys))

    for attr_key in node_attr_keys:
        node_dict[attr_key] = [
            node_attr[attr_key] if attr_key in node_attr.keys() else None
            for node_key, node_attr in list(G.nodes(data=True))
        ]

    # convert edge attributes
    edge_dict = dict()
    edge_dict['start'] = [x[0] for x in G.edges(data=True)]
    edge_dict['end'] = [x[1] for x in G.edges(data=True)]

    edge_attr_keys = [
        attr_key for edge in list(G.edges(data=True))
        for attr_key in edge[2].keys()
    ]
    edge_attr_keys = list(set(edge_attr_keys))

    for attr_key in edge_attr_keys:
        edge_dict[attr_key] = [
            edge_attr[attr_key] if attr_key in edge_attr.keys() else None
            for _, _, edge_attr in list(G.edges(data=True))
        ]

    node_source = ColumnDataSource(data=node_dict)
    edge_source = ColumnDataSource(data=edge_dict)

    TOOLTIPS = [("cluster1", "@start"), ("score", "@similarity_score"),
                ("cluster2", "@end")]

    plot = Plot(x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))

    plot.add_tools(HoverTool(tooltips=TOOLTIPS), WheelZoomTool(), TapTool(),
                   BoxSelectTool(), ResetTool(), PanTool(), BoxZoomTool())

    # networkx object >> bokeh
    graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0))

    graph_renderer.node_renderer.data_source.data = node_source.data
    graph_renderer.edge_renderer.data_source.data = edge_source.data

    # glyphs for nodes
    graph_renderer.node_renderer.glyph = Circle(size=15,
                                                fill_color=Spectral6[0])
    graph_renderer.node_renderer.selection_glyph = Circle(
        size=15, fill_color=Spectral6[2])
    graph_renderer.node_renderer.hover_glyph = Circle(size=15,
                                                      fill_color=Spectral6[1])

    # glyphs for edges
    graph_renderer.edge_renderer.glyph = MultiLine(line_color="#cccccc",
                                                   line_alpha=0.8,
                                                   line_width=3)
    graph_renderer.edge_renderer.selection_glyph = MultiLine(
        line_color=Spectral6[2], line_width=3)
    graph_renderer.edge_renderer.hover_glyph = MultiLine(
        line_color=Spectral6[1], line_width=5)

    graph_renderer.selection_policy = NodesAndLinkedEdges()
    graph_renderer.inspection_policy = EdgesAndLinkedNodes()

    plot.renderers.append(graph_renderer)

    output_file("output.html", title="Analysis {}".format(analysis_id))
    show(plot)

    resources = INLINE.render()
    script, div = components(plot)

    return plot, script, div, resources
Beispiel #29
0
def plot_scores(scores,
                legend=['Legend1', 'Legend2'],
                colors=['blue', 'green'],
                plot_title='Title',
                plot_xlabel='X',
                plot_ylabel='Y',
                plot_lib='matplotlib',
                matplotlib_style='default',
                fig_size=(800, 600),
                bokeh_notebook=False):
    if len(legend) == 0:
        for i in range(scores.shape[0]):
            legend.append('Legend ' + str(i))
    if len(colors) == 0:
        colors = [
            'blue', 'green', 'red', 'mediumvioletred', 'magenta', 'sienna',
            'maroon', 'brown'
        ]
    if plot_lib == 'matplotlib':
        plt.style.use(matplotlib_style)
        plt.grid()
        for i in range(scores.shape[0]):
            plt.plot(np.arange(len(scores[i, :])),
                     scores[i, :],
                     '-',
                     color=colors[i],
                     label=legend[i])
        plt.title(plot_title)
        plt.xlabel(plot_xlabel)
        plt.ylabel(plot_ylabel)
        plt.legend(loc='best')
        plt.axis('tight')
        plt.show()
    elif plot_lib == 'seaborn':
        fig = plt.figure(figsize=fig_size)
        plt.grid()
        sns_plot = sns.tsplot(data=scores,
                              err_style=['ci_band', 'ci_bars'],
                              marker='o',
                              legend=True)
        sns_plot.set(xlabel=plot_xlabel, ylabel=plot_ylabel)
        sns.plt.title(plot_title)
        if bokeh_notebook is True:
            output_notebook()
        show(mpl.to_bokeh(fig))
    elif plot_lib == 'bokeh':
        if bokeh_notebook is True:
            output_notebook()
        num_exp = scores.shape[1]
        num_splits = scores.shape[0]
        hover = HoverTool(tooltips=[
            ("(x, accuracy)", "($x, $y)"),
        ])
        p = figure(title=plot_title,
                   width=fig_size[1],
                   height=fig_size[0],
                   tools=[
                       hover,
                       PanTool(),
                       BoxZoomTool(),
                       ResetTool(),
                       WheelZoomTool(),
                       SaveTool()
                   ])
        p.xaxis.axis_label = plot_xlabel
        p.yaxis.axis_label = plot_ylabel
        p.title.text = plot_title
        p.background_fill = 'beige'
        for i in range(num_exp):
            source = ColumnDataSource(data=dict(
                x=np.arange(num_splits),
                y=scores[:, i],
            ))
            p.circle(np.arange(num_splits),
                     scores[i, :],
                     color=colors[i],
                     size=5,
                     line_alpha=0,
                     source=source)
            p.line(np.arange(num_splits),
                   scores[i, :],
                   color=colors[i],
                   legend=legend[i],
                   source=source)
        show(p)
Beispiel #30
0
def create(palm):
    doc = curdoc()

    # Calibration averaged waveforms per photon energy
    waveform_plot = Plot(
        title=Title(text="eTOF calibration waveforms"),
        x_range=DataRange1d(),
        y_range=DataRange1d(),
        plot_height=760,
        plot_width=PLOT_CANVAS_WIDTH,
        toolbar_location='right',
    )

    # ---- tools
    waveform_plot.toolbar.logo = None
    waveform_plot_hovertool = HoverTool(
        tooltips=[("energy, eV", '@en'), ("eTOF bin", '$x{0.}')])

    waveform_plot.add_tools(PanTool(), BoxZoomTool(), WheelZoomTool(),
                            ResetTool(), waveform_plot_hovertool)

    # ---- axes
    waveform_plot.add_layout(LinearAxis(axis_label='eTOF time bin'),
                             place='below')
    waveform_plot.add_layout(LinearAxis(axis_label='Intensity',
                                        major_label_orientation='vertical'),
                             place='left')

    # ---- grid lines
    waveform_plot.add_layout(Grid(dimension=0, ticker=BasicTicker()))
    waveform_plot.add_layout(Grid(dimension=1, ticker=BasicTicker()))

    # ---- multiline glyphs
    waveform_ref_source = ColumnDataSource(dict(xs=[], ys=[], en=[]))
    waveform_ref_multiline = waveform_plot.add_glyph(
        waveform_ref_source, MultiLine(xs='xs', ys='ys', line_color='blue'))

    waveform_str_source = ColumnDataSource(dict(xs=[], ys=[], en=[]))
    waveform_str_multiline = waveform_plot.add_glyph(
        waveform_str_source, MultiLine(xs='xs', ys='ys', line_color='red'))

    # ---- legend
    waveform_plot.add_layout(
        Legend(items=[(
            "reference",
            [waveform_ref_multiline]), ("streaked",
                                        [waveform_str_multiline])]))
    waveform_plot.legend.click_policy = "hide"

    # ---- vertical spans
    photon_peak_ref_span = Span(location=0,
                                dimension='height',
                                line_dash='dashed',
                                line_color='blue')
    photon_peak_str_span = Span(location=0,
                                dimension='height',
                                line_dash='dashed',
                                line_color='red')
    waveform_plot.add_layout(photon_peak_ref_span)
    waveform_plot.add_layout(photon_peak_str_span)

    # Calibration fit plot
    fit_plot = Plot(
        title=Title(text="eTOF calibration fit"),
        x_range=DataRange1d(),
        y_range=DataRange1d(),
        plot_height=PLOT_CANVAS_HEIGHT,
        plot_width=PLOT_CANVAS_WIDTH,
        toolbar_location='right',
    )

    # ---- tools
    fit_plot.toolbar.logo = None
    fit_plot.add_tools(PanTool(), BoxZoomTool(), WheelZoomTool(), ResetTool())

    # ---- axes
    fit_plot.add_layout(LinearAxis(axis_label='Photoelectron peak shift'),
                        place='below')
    fit_plot.add_layout(LinearAxis(axis_label='Photon energy, eV',
                                   major_label_orientation='vertical'),
                        place='left')

    # ---- grid lines
    fit_plot.add_layout(Grid(dimension=0, ticker=BasicTicker()))
    fit_plot.add_layout(Grid(dimension=1, ticker=BasicTicker()))

    # ---- circle glyphs
    fit_ref_circle_source = ColumnDataSource(dict(x=[], y=[]))
    fit_ref_circle = fit_plot.add_glyph(
        fit_ref_circle_source, Circle(x='x', y='y', line_color='blue'))
    fit_str_circle_source = ColumnDataSource(dict(x=[], y=[]))
    fit_str_circle = fit_plot.add_glyph(fit_str_circle_source,
                                        Circle(x='x', y='y', line_color='red'))

    # ---- line glyphs
    fit_ref_line_source = ColumnDataSource(dict(x=[], y=[]))
    fit_ref_line = fit_plot.add_glyph(fit_ref_line_source,
                                      Line(x='x', y='y', line_color='blue'))
    fit_str_line_source = ColumnDataSource(dict(x=[], y=[]))
    fit_str_line = fit_plot.add_glyph(fit_str_line_source,
                                      Line(x='x', y='y', line_color='red'))

    # ---- legend
    fit_plot.add_layout(
        Legend(items=[
            ("reference", [fit_ref_circle, fit_ref_line]),
            ("streaked", [fit_str_circle, fit_str_line]),
        ]))
    fit_plot.legend.click_policy = "hide"

    # Calibration results datatables
    def datatable_ref_source_callback(_attr, _old, new):
        for en, ps, use in zip(new['energy'], new['peak_pos_ref'],
                               new['use_in_fit']):
            palm.etofs['0'].calib_data.loc[
                en, 'calib_tpeak'] = ps if ps != 'NaN' else np.nan
            palm.etofs['0'].calib_data.loc[en, 'use_in_fit'] = use

        calib_res = {}
        for etof_key in palm.etofs:
            calib_res[etof_key] = palm.etofs[etof_key].fit_calibration_curve()
        update_calibration_plot(calib_res)

    datatable_ref_source = ColumnDataSource(
        dict(energy=['', '', ''],
             peak_pos_ref=['', '', ''],
             use_in_fit=[True, True, True]))
    datatable_ref_source.on_change('data', datatable_ref_source_callback)

    datatable_ref = DataTable(
        source=datatable_ref_source,
        columns=[
            TableColumn(field='energy',
                        title="Photon Energy, eV",
                        editor=IntEditor()),
            TableColumn(field='peak_pos_ref',
                        title="Reference Peak",
                        editor=IntEditor()),
            TableColumn(field='use_in_fit',
                        title=" ",
                        editor=CheckboxEditor(),
                        width=80),
        ],
        index_position=None,
        editable=True,
        height=300,
        width=250,
    )

    def datatable_str_source_callback(_attr, _old, new):
        for en, ps, use in zip(new['energy'], new['peak_pos_str'],
                               new['use_in_fit']):
            palm.etofs['1'].calib_data.loc[
                en, 'calib_tpeak'] = ps if ps != 'NaN' else np.nan
            palm.etofs['1'].calib_data.loc[en, 'use_in_fit'] = use

        calib_res = {}
        for etof_key in palm.etofs:
            calib_res[etof_key] = palm.etofs[etof_key].fit_calibration_curve()
        update_calibration_plot(calib_res)

    datatable_str_source = ColumnDataSource(
        dict(energy=['', '', ''],
             peak_pos_str=['', '', ''],
             use_in_fit=[True, True, True]))
    datatable_str_source.on_change('data', datatable_str_source_callback)

    datatable_str = DataTable(
        source=datatable_str_source,
        columns=[
            TableColumn(field='energy',
                        title="Photon Energy, eV",
                        editor=IntEditor()),
            TableColumn(field='peak_pos_str',
                        title="Streaked Peak",
                        editor=IntEditor()),
            TableColumn(field='use_in_fit',
                        title=" ",
                        editor=CheckboxEditor(),
                        width=80),
        ],
        index_position=None,
        editable=True,
        height=350,
        width=250,
    )

    # eTOF calibration folder path text input
    def path_textinput_callback(_attr, _old, _new):
        path_periodic_update()
        update_load_dropdown_menu()

    path_textinput = TextInput(title="eTOF calibration path:",
                               value=os.path.join(os.path.expanduser('~')),
                               width=510)
    path_textinput.on_change('value', path_textinput_callback)

    # eTOF calibration eco scans dropdown
    def scans_dropdown_callback(_attr, _old, new):
        scans_dropdown.label = new

    scans_dropdown = Dropdown(label="ECO scans",
                              button_type='default',
                              menu=[])
    scans_dropdown.on_change('value', scans_dropdown_callback)

    # ---- etof scans periodic update
    def path_periodic_update():
        new_menu = []
        if os.path.isdir(path_textinput.value):
            for entry in os.scandir(path_textinput.value):
                if entry.is_file() and entry.name.endswith('.json'):
                    new_menu.append((entry.name, entry.name))
        scans_dropdown.menu = sorted(new_menu, reverse=True)

    doc.add_periodic_callback(path_periodic_update, 5000)

    # Calibrate button
    def calibrate_button_callback():
        try:
            palm.calibrate_etof_eco(eco_scan_filename=os.path.join(
                path_textinput.value, scans_dropdown.value))
        except Exception:
            palm.calibrate_etof(folder_name=path_textinput.value)

        datatable_ref_source.data.update(
            energy=palm.etofs['0'].calib_data.index.tolist(),
            peak_pos_ref=palm.etofs['0'].calib_data['calib_tpeak'].tolist(),
            use_in_fit=palm.etofs['0'].calib_data['use_in_fit'].tolist(),
        )

        datatable_str_source.data.update(
            energy=palm.etofs['0'].calib_data.index.tolist(),
            peak_pos_str=palm.etofs['1'].calib_data['calib_tpeak'].tolist(),
            use_in_fit=palm.etofs['1'].calib_data['use_in_fit'].tolist(),
        )

    def update_calibration_plot(calib_res):
        etof_ref = palm.etofs['0']
        etof_str = palm.etofs['1']

        shift_val = 0
        etof_ref_wf_shifted = []
        etof_str_wf_shifted = []
        for wf_ref, wf_str in zip(etof_ref.calib_data['waveform'],
                                  etof_str.calib_data['waveform']):
            shift_val -= max(wf_ref.max(), wf_str.max())
            etof_ref_wf_shifted.append(wf_ref + shift_val)
            etof_str_wf_shifted.append(wf_str + shift_val)

        waveform_ref_source.data.update(
            xs=len(etof_ref.calib_data) *
            [list(range(etof_ref.internal_time_bins))],
            ys=etof_ref_wf_shifted,
            en=etof_ref.calib_data.index.tolist(),
        )

        waveform_str_source.data.update(
            xs=len(etof_str.calib_data) *
            [list(range(etof_str.internal_time_bins))],
            ys=etof_str_wf_shifted,
            en=etof_str.calib_data.index.tolist(),
        )

        photon_peak_ref_span.location = etof_ref.calib_t0
        photon_peak_str_span.location = etof_str.calib_t0

        def plot_fit(time, calib_a, calib_b):
            time_fit = np.linspace(np.nanmin(time), np.nanmax(time), 100)
            en_fit = (calib_a / time_fit)**2 + calib_b
            return time_fit, en_fit

        def update_plot(calib_results, circle, line):
            (a, c), x, y = calib_results
            x_fit, y_fit = plot_fit(x, a, c)
            circle.data.update(x=x, y=y)
            line.data.update(x=x_fit, y=y_fit)

        update_plot(calib_res['0'], fit_ref_circle_source, fit_ref_line_source)
        update_plot(calib_res['1'], fit_str_circle_source, fit_str_line_source)

        calib_const_div.text = """
        a_str = {:.2f}<br>
        b_str = {:.2f}<br>
        <br>
        a_ref = {:.2f}<br>
        b_ref = {:.2f}
        """.format(etof_str.calib_a, etof_str.calib_b, etof_ref.calib_a,
                   etof_ref.calib_b)

    calibrate_button = Button(label="Calibrate eTOF",
                              button_type='default',
                              width=250)
    calibrate_button.on_click(calibrate_button_callback)

    # Photon peak noise threshold value text input
    def phot_peak_noise_thr_textinput_callback(_attr, old, new):
        try:
            new_value = float(new)
            if new_value > 0:
                for etof in palm.etofs.values():
                    etof.photon_peak_noise_thr = new_value
            else:
                phot_peak_noise_thr_textinput.value = old

        except ValueError:
            phot_peak_noise_thr_textinput.value = old

    phot_peak_noise_thr_textinput = TextInput(
        title='Photon peak noise threshold:', value=str(1))
    phot_peak_noise_thr_textinput.on_change(
        'value', phot_peak_noise_thr_textinput_callback)

    # Electron peak noise threshold value text input
    def el_peak_noise_thr_textinput_callback(_attr, old, new):
        try:
            new_value = float(new)
            if new_value > 0:
                for etof in palm.etofs.values():
                    etof.electron_peak_noise_thr = new_value
            else:
                el_peak_noise_thr_textinput.value = old

        except ValueError:
            el_peak_noise_thr_textinput.value = old

    el_peak_noise_thr_textinput = TextInput(
        title='Electron peak noise threshold:', value=str(10))
    el_peak_noise_thr_textinput.on_change(
        'value', el_peak_noise_thr_textinput_callback)

    # Save calibration button
    def save_button_callback():
        palm.save_etof_calib(path=path_textinput.value)
        update_load_dropdown_menu()

    save_button = Button(label="Save", button_type='default', width=250)
    save_button.on_click(save_button_callback)

    # Load calibration button
    def load_dropdown_callback(_attr, _old, new):
        if new:
            palm.load_etof_calib(os.path.join(path_textinput.value, new))

            datatable_ref_source.data.update(
                energy=palm.etofs['0'].calib_data.index.tolist(),
                peak_pos_ref=palm.etofs['0'].calib_data['calib_tpeak'].tolist(
                ),
                use_in_fit=palm.etofs['0'].calib_data['use_in_fit'].tolist(),
            )

            datatable_str_source.data.update(
                energy=palm.etofs['0'].calib_data.index.tolist(),
                peak_pos_str=palm.etofs['1'].calib_data['calib_tpeak'].tolist(
                ),
                use_in_fit=palm.etofs['1'].calib_data['use_in_fit'].tolist(),
            )

            # Drop selection, so that this callback can be triggered again on the same dropdown menu
            # item from the user perspective
            load_dropdown.value = ''

    def update_load_dropdown_menu():
        new_menu = []
        calib_file_ext = '.palm_etof'
        if os.path.isdir(path_textinput.value):
            for entry in os.scandir(path_textinput.value):
                if entry.is_file() and entry.name.endswith((calib_file_ext)):
                    new_menu.append(
                        (entry.name[:-len(calib_file_ext)], entry.name))
            load_dropdown.button_type = 'default'
            load_dropdown.menu = sorted(new_menu, reverse=True)
        else:
            load_dropdown.button_type = 'danger'
            load_dropdown.menu = new_menu

    doc.add_next_tick_callback(update_load_dropdown_menu)
    doc.add_periodic_callback(update_load_dropdown_menu, 5000)

    load_dropdown = Dropdown(label="Load", menu=[], width=250)
    load_dropdown.on_change('value', load_dropdown_callback)

    # eTOF fitting equation
    fit_eq_div = Div(
        text="""Fitting equation:<br><br><img src="/palm/static/5euwuy.gif">"""
    )

    # Calibration constants
    calib_const_div = Div(text="""
        a_str = {}<br>
        b_str = {}<br>
        <br>
        a_ref = {}<br>
        b_ref = {}
        """.format(0, 0, 0, 0))

    # assemble
    tab_layout = column(
        row(
            column(waveform_plot, fit_plot),
            Spacer(width=30),
            column(
                path_textinput,
                scans_dropdown,
                calibrate_button,
                phot_peak_noise_thr_textinput,
                el_peak_noise_thr_textinput,
                row(save_button, load_dropdown),
                row(datatable_ref, datatable_str),
                calib_const_div,
                fit_eq_div,
            ),
        ))

    return Panel(child=tab_layout, title="eTOF Calibration")