Listings Völkl/Jupyter-Notebook-Widgets

Listing 1: ipyleaflet-Layer laden
import os
import json
import random

with open('vg250_lan.geojson', 'r') as f:
    data = json.load(f)

def random_color(feature):
    return {
        'color': 'black',
        'fillColor': random.choice(['red', 'yellow', 'green', 'orange']),
    }


geo_json = GeoJSON(
    data=data,
    name='geojson',
    style={
        'opacity': 1,  'fillOpacity': 0.1, 'weight': 1
    },
    hover_style={
        'color': 'white', 'fillOpacity': 0.5
    },
    style_callback=random_color
)
m.add_layer(geo_json)

-----

Listing 2: Wetterdaten via onecall abfragen
import requests
import json

api_key = '..' #hier der persönliche API Key

lat = marker.location[0]
lon = marker.location[1]
lang = 'de'
url = f'https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&appid={api_key}&units=metric&lang={lang}'

reponse = requests.get(url)
data = json.loads(reponse.text)
print(json.dumps(data, indent=4))

-----

Listing 3:  Hintergrund mit einfachem Rand erzeugen
canvas = multi_canvas[0]

canvas.clear()

canvas.fill_style = 'lightblue'
canvas.fill_rect(0, 0, canvas.width, canvas.height)

distance = 20 
canvas.stroke_style = 'white'
canvas.stroke_rect(distance, distance, canvas.width-2*distance, canvas.height - 2 * distance)

weather = 'sun'

canvas = multi_canvas[1]

canvas.clear()

if weather == 'sun':
    #circle
    canvas.fill_style = 'yellow'
    canvas.fill_circle(120,140,40)
elif weather == 'rain':
    #lines
    canvas.line_width = 5
    canvas.stroke_style = 'gray'
    canvas.line_cap= 'round'
    canvas.set_line_dash([10, 10])
    for i in range(4):
        canvas.stroke_line(140+10*i, 40+10*i, 40+10*i, 140+10*i)
elif weather == 'cloud':
    #path
    canvas.fill_style = 'white'
    canvas.begin_path()
    canvas.move_to(75, 40)
    canvas.bezier_curve_to(75, 37, 70, 25, 50, 25)
    canvas.bezier_curve_to(20, 25, 20, 62.5, 20, 62.5)
    canvas.bezier_curve_to(20, 80, 40, 102, 75, 100)
    canvas.bezier_curve_to(110, 102, 130, 80, 130, 62.5)
    canvas.bezier_curve_to(130, 62.5, 130, 25, 100, 25)
    canvas.bezier_curve_to(85, 25, 75, 37, 75, 40)
    canvas.fill()

canvas = multi_canvas[2]

canvas.clear()

temperatur = '3.25 °C'
description = 'Klarer Himmel'

canvas.font = '48px serif'
canvas.stroke_style = 'white'
canvas.stroke_text(temperatur, 24, 240)

canvas.font = '24px serif'
canvas.fill_style = 'white'
canvas.fill_text(description, 24, 320)

-----

Listing 4: Mit lines auf die Zeichnung zugreifen
%matplotlib widget
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import VBox, FloatSlider

plt.ioff()

slider = FloatSlider(
    orientation='horizontal',
    description='m:',
    value=1.0,
    continuous_update=False,
    min=0,
    max=+10
)

fig = plt.figure()
plt.title('{} * x'.format(slider.value))
x = np.linspace(0, 20, 500)
lines = plt.plot(x, slider.value * x)

def update_lines(change):
    plt.title('{} * x'.format(change.new))
    lines[0].set_data(x, change.new * x)
    fig.canvas.draw()
    fig.canvas.flush_events()

slider.observe(update_lines, names='value')

display(fig.canvas,slider)

-----

Listing 5: Interaktion mit observe implementieren
from ipysheet import sheet, cell
import ipywidgets as widgets

s = sheet(rows=3, columns=2)
cell_a = cell(0, 1, 1,label_left='a')
cell_b = cell(1, 1, 2,label_left='b')
cell_sum = cell(2, 1, 3, label_left='sum', read_only=True)

slider = widgets.FloatSlider(min=-10, max=10, description='a')
widgets.jslink((cell_a, 'value'), (slider, 'value'))

def calculate(change):
    cell_sum.value = cell_a.value + cell_b.value

cell_a.observe(calculate, 'value')
cell_b.observe(calculate, 'value')

display(s, slider)

-----

Listing 6: Via HTML-Template auf Vuetify zugreifen
import ipyvuetify as v

from traitlets import Unicode

class ProgressCircular(v.VuetifyTemplate):
    template = Unicode('''
        <template>
          <div class="text-center">
            <v-progress-circular
              :rotate="360"
              :size="size"
              :width="15"
              :value="value"
              color="teal"
            >
              {{ value }}
            </v-progress-circular>
          </div>
        </template>
        ''').tag(sync=True)
    value = Unicode('78').tag(sync=True)
    size = Unicode('100').tag(sync=True)
    
w = ProgressCircular()

-----

Listing 7: Eigene Klasse WeatherApp von einem Widget ableiten
%matplotlib widget
import ipywidgets as widgets
from ipyleaflet import *
from ipycanvas import MultiCanvas
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from ipysheet import sheet, cell, row, column, cell_range

import requests
import json
import numpy as np
from datetime import datetime
import pytz


class Component:
    def update(self, data):
        pass
    
class OuputToday(Component):
    def __init__(self):
        self.multi_canvas = MultiCanvas(4, width=200, height=400)
        
        canvas = self.multi_canvas[0]

        canvas.clear()

        canvas.fill_style = 'lightblue'
        canvas.fill_rect(0, 0, canvas.width, canvas.height)

        distance = 20 
        canvas.stroke_style = 'white'
        canvas.stroke_rect(distance, distance, canvas.width-2*distance, canvas.height - 2 * distance)
    
    def update(self, data):
        weather = int(data.data['current']['weather'][0]['id'])
        description  = data.data['current']['weather'][0]['description']
        temp  = data.data['current']['temp']

        canvas = self.multi_canvas[1]

        canvas.clear()

        if weather == 800: # 'sun'
            #circle
            canvas.fill_style = 'yellow'
            canvas.fill_circle(120,140,40)
        elif weather <= 600:  # 'rain'
            #lines
            canvas.line_width = 5
            canvas.stroke_style = 'gray'
            canvas.line_cap= 'round'
            canvas.set_line_dash([10, 10])
            for i in range(4):
                canvas.stroke_line(140+10*i, 40+10*i, 40+10*i, 140+10*i)
        elif weather >800: # 'cloud'
            #path
            canvas.fill_style = 'white'
            canvas.begin_path()
            canvas.move_to(75, 40)
            canvas.bezier_curve_to(75, 37, 70, 25, 50, 25)
            canvas.bezier_curve_to(20, 25, 20, 62.5, 20, 62.5)
            canvas.bezier_curve_to(20, 80, 40, 102, 75, 100)
            canvas.bezier_curve_to(110, 102, 130, 80, 130, 62.5)
            canvas.bezier_curve_to(130, 62.5, 130, 25, 100, 25)
            canvas.bezier_curve_to(85, 25, 75, 37, 75, 40)
            canvas.fill()
            
        canvas = self.multi_canvas[2]

        canvas.clear()

        temperatur = '3.25 °C'
        description = 'Klarer Himmel'

        canvas.font = '48px serif'
        canvas.stroke_style = 'white'
        canvas.stroke_text(temperatur, 24, 240)

        canvas.font = '24px serif'
        canvas.fill_style = 'white'
        canvas.fill_text(description, 24, 320)
    

class HourlyForecast(Component):
    def __init__(self, data):
        plt.ioff()

        fig = plt.figure()
        plt.title('Stündliche Vorhersage')
        plt.xlabel('Zeit')
        plt.ylabel('Temperatur')

        self.hourly_lines = plt.plot(data.hourly_dt, data.hourly_temp)
        
        plt.xticks(rotation=90)
        plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))

        fig.canvas.toolbar_visible = True
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False
        fig.canvas.toolbar_position = 'bottom'
        
        self.fig = fig
        self.canvas = self.fig.canvas
        
    def update(self, data):
        self.hourly_lines[0].set_data(data.hourly_dt, data.hourly_temp)
        self.canvas.draw()
        self.canvas.flush_events()

class DailyForecast(Component):
    def __init__(self):
        self.sheet = sheet(rows=8, columns=4, row_headers=False, column_headers=False)
        
    def update(self, data):
        column(0, data.daily_dt, start_row=1, read_only=True)
        column(1, [widgets.Image.from_url(f'http://openweathermap.org/img/wn/{x}@2x.png') for x in data.daily_icon], style={'max-width': '10px', 'min-height': '10px'})
        column(2, data.daily_temp, start_row=1, read_only=True)
        column(3, data.daily_description, start_row=1, read_only=True)

class Data:
    def __init__(self):
        self.data = None
        self.hourly_dt = []
        self.hourly_temp = []
        self.hourly_lines = None
        
        self.daily_dt = []
        self.daily_icon = []
        self.daily_temp = []
        self.daily_description = []
        
    def read(self,point):
        api_key = 'e1768a35a0fb67871b16d884cfbc86de'
        lat = point[0]
        lon = point[1]
        lang = 'de'
        url = f'https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&appid={api_key}&units=metric&lang={lang}'

        reponse = requests.get(url)
        self.data = json.loads(reponse.text)
        
        hourly = self.data['hourly']

        self.hourly_dt = []
        self.hourly_temp = []
    
        for e in hourly:
            self.hourly_dt.append(datetime.fromtimestamp(e['dt'],pytz.timezone('Europe/Berlin')))
            self.hourly_temp.append(e['temp'])
            
        daily = self.data['daily']

        self.daily_dt = []
        self.daily_icon = []
        self.daily_temp = []
        self.daily_description = []

        for e in daily:
            self.daily_dt.append(datetime.fromtimestamp(e['dt'],pytz.timezone('Europe/Berlin')).strftime('%a, %b %d'))
            self.daily_icon.append(e['weather'][0]['icon'])
            self.daily_temp.append(f'{e["temp"]["min"]} {e["temp"]["max"]}°C')
            self.daily_description.append(e['weather'][0]['description'])


class WeatherApp(widgets.VBox):
    def __init__(self):
        super().__init__()
        
        self.data = Data()
        
        self.input_map = self.create_input_map()
        self.data.read(self.input_map.center)
        
        self.output_today = OuputToday()
        self.hourly_forecast = HourlyForecast(self.data)
        self.daily_forecast = DailyForecast()
        
        self.update(self.input_map.center)
        
        tab = widgets.Tab(layout=widgets.Layout(width='100%'))
        tab.children = [widgets.Box([self.hourly_forecast.canvas]), self.daily_forecast.sheet]
        for i,e in enumerate(('stündlich', 'täglich')):
            tab.set_title(i, e)
        
        box_1 = widgets.HBox([self.output_today.multi_canvas, tab])
        self.children = [self.input_map, box_1]
        
    def create_input_map(self):
        input_map = Map(center=(49.017222, 12.096944), zoom=6, basemap = basemaps.OpenStreetMap.Mapnik)
        control = LayersControl(position='topright')
        input_map.add_control(control)
        marker = Marker(title="location", location=input_map.center, draggable=True)
        input_map.add_layer(marker)
        marker.observe(self.on_marker_changed, 'location')
        return input_map
    
    def on_marker_changed(self, change):
        self.update(change.new)
        
        
    def create_daily_forecast(self):
        return sheet(rows=8, columns=4, row_headers=False, column_headers=False)
        
    def update(self, location):
        self.data.read(location)
        self.output_today.update(self.data)
        self.hourly_forecast.update(self.data)
        self.daily_forecast.update(self.data)

