<jupyter_start><jupyter_text>COVID-19 DynamicsThere are many visiualizations of the ongoing [COVID-19](https://en.wikipedia.org/wiki/Coronavirus_disease_2019) (or Corona) virus outbreak including this very popular [ARCGIS one](https://gisanddata.maps.arcgis.com/apps/opsdashboard/index.html/bda7594740fd40299423467b48e9ecf6). As most are static, this one specifically aims to illustrate the dynamics of the COVID-19 virus infection using the official datasets available at https://github.com/CSSEGISandData/COVID-19. It builds on [Jupyter](https://jupyter.org), [IPyLeaflet](https://github.com/jupyter-widgets/ipyleaflet), [Pandas](https://pandas.pydata.org/), [Voilà](https://github.com/voila-dashboards/voila) plus a little [ReportLab](https://reportlab.com), and running on [MyBinder](https://mybinder.org), too.<jupyter_code>from collections import OrderedDict
from functools import partial

from ipyleaflet import basemap_to_tiles, basemaps, CircleMarker, \
    FullScreenControl, LayersControl, LayerGroup, Map, Marker, \
    Popup, WidgetControl
from ipywidgets import IntSlider, HBox, HTML, jslink, Layout, Output, \
    Play, Image
import pandas as pd
from covid19 import radius_sphere, create_qrcode
base = ("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/"
       "master/csse_covid_19_data/csse_covid_19_time_series/")

df_confirmed = pd.read_csv(base + "time_series_19-covid-Confirmed.csv")
df_deaths = pd.read_csv(base + "time_series_19-covid-Deaths.csv")
df_recovered = pd.read_csv(base + "time_series_19-covid-Recovered.csv")

df_confirmed["Province/State"].fillna("", inplace=True)
df_deaths["Province/State"].fillna("", inplace=True)
df_recovered["Province/State"].fillna("", inplace=True)
# df.describe()
# df.head()
# same for all dataframes
df = df_recovered
provinces = df["Province/State"]
countries = df["Country/Region"]
locations = list(zip(df.Lat, df.Long))
day_cols = [col for col in df.columns if col.count("/") == 2]
print(f"Data range: {day_cols[0]} – {day_cols[-1]}.")
def slider_changed(change, the_map=None, output=None, slider=None, data=None):
    day = change['new']

    if slider:
        slider.description = day_cols[day]
        
    for key, val in data.items():
        cat = key
        df = val["df"]
        group = val["markers"]
        values = df[day_cols[day]]
        color = val["color"]
        
        circle_layers = list(l for l in group.layers if type(l) == CircleMarker)
        if not circle_layers:
            # if output:
            #     with output:
            #         print(f"updating {day_cols[day]}")
            markers = []
            for i, loc in enumerate(locations):
                place = countries[i] if not provinces[i] else f"{provinces[i]}, {countries[i]}"
                rad = int(radius_sphere(values[i]))
                marker = CircleMarker(
                    location=tuple(loc),
                    radius=rad,
                    weight=0,
                    color=color if rad > 0 else "white",
                    opacity=rad > 0)
                message = HTML(value = f"<b>{day_cols[day]}: {values[i]} {cat} in {place}</b>")
                marker.popup = message
                markers.append(marker)
            group.layers = tuple(markers)
        else:
            for i, marker in enumerate(circle_layers):
                place = countries[i] if not provinces[i] else f"{provinces[i]}, {countries[i]}"
                rad = int(radius_sphere(values[i]))
                marker.radius = rad
                marker.color=color if rad > 0 else "white"
                marker.opacity = rad > 0
                marker.weight = 0
                marker.popup.value = f"<b>{day_cols[day]}: {values[i]} {cat} in {place}</b>"
data = OrderedDict(
    confirmed = {
        "markers": LayerGroup(layers=[], name="Confirmed"),
        "df": df_confirmed,
        "color": "red"
    },
    deaths = {
        "markers": LayerGroup(layers=[], name="Deaths"),
        "df": df_deaths,
        "color": "black"
    },
    recovered = {
        "markers": LayerGroup(layers=[], name="Recovered"),
        "df": df_recovered,
        "color": "green"
    },
)
# output = Output()
# display(output)

m = Map(zoom=2, basemap=basemaps.CartoDB.Positron)

m += FullScreenControl()

# dark_matter_layer = basemap_to_tiles(basemaps.CartoDB.DarkMatter)
# m.add_layer(dark_matter_layer)
# nat_geo_world_layer = basemap_to_tiles(basemaps.Esri.NatGeoWorldMap)
# m.add_layer(nat_geo_world_layer)
layers_control = LayersControl(position='topright')
m += layers_control

# markers_confirmed = LayerGroup(layers=[], name=category)
# m += markers_confirmed

for key, val in data.items():
    m += val["markers"]

play = Play(
    value=0,
    min=0,
    max=len(day_cols)-1,
    step=1,
    interval=500,
    # description="Press play",
    disabled=False
)
day_slider = IntSlider(
    description='Day:',
    layout=Layout(width="600px"),
    min=-1, max=len(day_cols)-1, value=-1)
jslink((play, 'value'), (day_slider, 'value'))
cb = partial(slider_changed,
             the_map=m,
             slider=day_slider,
             # output=output,
             data=data
)
day_slider.observe(cb, names='value')
widget_control1 = WidgetControl(
    widget=HBox([play, day_slider]),
    position='bottomleft',
    layout=Layout(width="600px"),
)
day_slider.value = 0
day_slider.min = 0
m.add_control(widget_control1)
widget_control2 = WidgetControl(
    widget=create_qrcode("https://bit.ly/3dgCR7h"),
    position='bottomleft',
    # layout=Layout(width="600px"),
)
m.add_control(widget_control2)
m<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1382751247327156, 'fraction_numerical': 0.015146115466856735, 'mean_word_length': 3.1670378619153676, 'pattern_counts': {'":': 9, '<': 9, '<?xml version=': 0, '>': 13, 'https://': 11, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '519347', 'license': 'MIT', 'n_tokens_mistral': 1945, 'n_tokens_neox': 1844, 'n_words': 370, 'path': 'dynamics1.ipynb', 'repo_name': 'deeplook/covid-19', 'size': '5612'}