Commit 2e705924 authored by Nane Kratzke's avatar Nane Kratzke
Browse files

Deploy

parent 3cfb4876
......@@ -15,6 +15,7 @@ Web-APIs sind Schnittstellen, um Informationen und Anwendungsfunktionen Nutzern
- [Vorbereitung](#vorbereitung)
- [Übung 01: Bauen Sie einen einfachen REST-Service](#übung-01-bauen-sie-einen-einfachen-rest-service)
- [Übung 02: Ergänzen Sie die Datenbank des Tutorials](#übung-02-ergänzen-sie-die-datenbank-des-tutorials)
- [Übung 03: Ergänzen Sie nun eine grafische Auswertung](#übung-03-ergänzen-sie-nun-eine-grafische-auswertung)
## Vorbereitung
......@@ -76,7 +77,41 @@ Committen und Pushen Sie nun diese Lösung in Git, um die Deployment Pipeline zu
- Geben Sie in Lens nun `kubectl port-forward svc/api-svc 8888:5000` ein.
- Öffnen Sie dann in einem Web-Browser die URL [http://localhost:8888](http://localhost:8888). Sie sollten dann erneut eine *"Distant Reading Archive"* Website sehen! Diesmal jedoch aus Kubernetes geliefert.
Setzen Sie nun das [Tutorial][tutorial] bis zum Punkt *API Design Principles* fort.
Setzen Sie nun das [Tutorial][tutorial] bis zum Punkt *API Design Principles* fort. Sie sollten nun den Book-API-Service in Kubernetes deployen. Wenn Sie mittels `kubectl port-forward svc/api-svc 5000:5000` die Book-API auf Ihren lokalen Rechner forwarden, sollten Sie folgende Links ohne Fehlermeldung abfragen können.
- [http://127.0.0.1:5000/api/v1/resources/books?id=1](http://127.0.0.1:5000/api/v1/resources/books?id=1) (Das Buch mit der Id 1)
- [http://127.0.0.1:5000/api/v1/resources/books/all](http://127.0.0.1:5000/api/v1/resources/books/all) (Alle Bücher)
> __Hinweis:__
> Wenn Sie gar nicht weiter wissen, können Sie mit `cp cheat/api-ue1.py api/api.py` ggf. etwas abkürzen.
## Übung 02: Ergänzen Sie die Datenbank des Tutorials
Setzen Sie nun das [Tutorial][tutorial] fort. Arbeiten Sie erst den Punkt *API Design Principles* durch. Machen Sie dann daran, die Datenbank zu integrieren.
Das Tutorial stellt eine sqlite-Datenbank mit diversen Buchpublikationen zur Verfügung. Diese ist in diesem Repository schon unter `api/books.db` hinterlegt und muss durch Sie nicht herunter geladen werden.
Sie sollten dann einen größeren Buchbestand flexibler abfragen können.
- [http://127.0.0.1:5000/api/v1/resources/books/all](http://127.0.0.1:5000/api/v1/resources/books/all)
- [http://127.0.0.1:5000/api/v1/resources/books?author=Connie+Willis](http://127.0.0.1:5000/api/v1/resources/books?author=Connie+Willis)
- [http://127.0.0.1:5000/api/v1/resources/books?author=Connie+Willis&published=1999](http://127.0.0.1:5000/api/v1/resources/books?author=Connie+Willis&published=1999)
- [http://127.0.0.1:5000/api/v1/resources/books?published=2010](http://127.0.0.1:5000/api/v1/resources/books?published=2010)
> __Hinweis:__
> Wenn Sie gar nicht weiter wissen, können Sie mit `cp cheat/api-ue2.py api/api.py` ggf. etwas abkürzen.
## Übung 03: Ergänzen Sie nun eine grafische Auswertung
Ergänzen Sie nun eine Route `/api/v1/resources/books/plot`. Diese soll nun die grafische Auswertung der Satzlängen über die Jahre des [Tutorials][tutorial] liefern. Das Datenformat soll SVG (Scalable Vector Grafic) sein.
Wenn Sie mittels `kubectl port-forward svc/api-svc 5000:5000` die Book-API auf Ihren lokalen Rechner forwarden, sollten Sie für folgende URL
- [http://127.0.0.1:5000/api/v1/resources/books/plot](http://127.0.0.1:5000/api/v1/resources/books/plot)
in etwa folgende Darstellung im Browser erhalten.
![TOP-10](top-10.svg)
> __Hinweis:__
> Wenn Sie gar nicht weiter wissen, können Sie mit `cp cheat/api-ue3.py api/api.py` ggf. etwas abkürzen.
\ No newline at end of file
FROM python:3.9-alpine
FROM python:3.9
COPY . /app/
RUN pip3 install flask
RUN pip3 install flask matplotlib
COPY . /app/
WORKDIR /app
EXPOSE 5000
ENTRYPOINT ["python3", "-u", "/app/api.py"]
ENTRYPOINT ["python3", "-u", "api-ue3.py"]
import flask
from flask import request, jsonify
import sqlite3
import matplotlib.pyplot as plt
import numpy as np
import string, re
app = flask.Flask(__name__)
app.config["DEBUG"] = True
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
@app.route('/', methods=['GET'])
def home():
return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''
@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
all_books = cur.execute('SELECT * FROM books;').fetchall()
return jsonify(all_books)
@app.route('/api/v1/resources/books/plot', methods=['GET'])
def book_plot():
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
all_books = cur.execute('SELECT * FROM books;').fetchall()
ys = np.array([len(book['first_sentence']) for book in all_books])
xs = np.array([book['published'] for book in all_books])
m, b = np.polyfit(xs, ys, 1)
fig = plt.figure()
plt.scatter(xs, ys, color="red")
plt.plot(xs, m * xs + b, color="blue")
plt.xlabel("Date of Publication")
plt.ylabel("Length of First Sentence in Characters")
plt.savefig("top-10.svg")
return flask.Response(open("top-10.svg").read(), mimetype='image/svg+xml')
@app.errorhandler(404)
def page_not_found(e):
return "<h1>404</h1><p>The resource could not be found.</p>", 404
@app.route('/api/v1/resources/books', methods=['GET'])
def api_filter():
query_parameters = request.args
id = query_parameters.get('id')
published = query_parameters.get('published')
author = query_parameters.get('author')
query = "SELECT * FROM books WHERE"
to_filter = []
if id:
query += ' id=? AND'
to_filter.append(id)
if published:
query += ' published=? AND'
to_filter.append(published)
if author:
query += ' author=? AND'
to_filter.append(author)
if not (id or published or author):
return page_not_found(404)
query = query[:-4] + ';'
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
results = cur.execute(query, to_filter).fetchall()
return jsonify(results)
app.run(host="0.0.0.0")
\ No newline at end of file
import flask
from flask import request, jsonify
app = flask.Flask(__name__)
# Create some test data for our catalog in the form of a list of dictionaries.
books = [
{'id': 0,
'title': 'A Fire Upon the Deep',
'author': 'Vernor Vinge',
'first_sentence': 'The coldsleep itself was dreamless.',
'year_published': '1992'},
{'id': 1,
'title': 'The Ones Who Walk Away From Omelas',
'author': 'Ursula K. Le Guin',
'first_sentence': 'With a clamor of bells that set the swallows soaring, the Festival of Summer came to the city Omelas, bright-towered by the sea.',
'published': '1973'},
{'id': 2,
'title': 'Dhalgren',
'author': 'Samuel R. Delany',
'first_sentence': 'to wound the autumnal city.',
'published': '1975'}
]
@app.route('/', methods=['GET'])
def home():
return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''
@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
return jsonify(books)
@app.route('/api/v1/resources/books', methods=['GET'])
def api_id():
# Check if an ID was provided as part of the URL.
# If ID is provided, assign it to a variable.
# If no ID is provided, display an error in the browser.
if 'id' in request.args:
id = int(request.args['id'])
else:
return "Error: No id field provided. Please specify an id."
# Create an empty list for our results
results = []
# Loop through the data and match results that fit the requested ID.
# IDs are unique, but other fields might return many results
for book in books:
if book['id'] == id:
results.append(book)
# Use the jsonify function from Flask to convert our list of
# Python dictionaries to the JSON format.
return jsonify(results)
app.run(host="0.0.0.0")
\ No newline at end of file
import flask
from flask import request, jsonify
import sqlite3
app = flask.Flask(__name__)
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
@app.route('/', methods=['GET'])
def home():
return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''
@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
all_books = cur.execute('SELECT * FROM books;').fetchall()
return jsonify(all_books)
@app.errorhandler(404)
def page_not_found(e):
return "<h1>404</h1><p>The resource could not be found.</p>", 404
@app.route('/api/v1/resources/books', methods=['GET'])
def api_filter():
query_parameters = request.args
id = query_parameters.get('id')
published = query_parameters.get('published')
author = query_parameters.get('author')
query = "SELECT * FROM books WHERE"
to_filter = []
if id:
query += ' id=? AND'
to_filter.append(id)
if published:
query += ' published=? AND'
to_filter.append(published)
if author:
query += ' author=? AND'
to_filter.append(author)
if not (id or published or author):
return page_not_found(404)
query = query[:-4] + ';'
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
results = cur.execute(query, to_filter).fetchall()
return jsonify(results)
app.run(host="0.0.0.0")
\ No newline at end of file
import flask
from flask import request, jsonify
import sqlite3
import matplotlib.pyplot as plt
import numpy as np
import string, re
app = flask.Flask(__name__)
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
@app.route('/', methods=['GET'])
def home():
return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''
@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
all_books = cur.execute('SELECT * FROM books;').fetchall()
return jsonify(all_books)
@app.route('/api/v1/resources/books/plot', methods=['GET'])
def book_plot():
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
all_books = cur.execute('SELECT * FROM books;').fetchall()
ys = np.array([len(book['first_sentence']) for book in all_books])
xs = np.array([book['published'] for book in all_books])
m, b = np.polyfit(xs, ys, 1)
fig = plt.figure()
plt.scatter(xs, ys, color="red")
plt.plot(xs, m * xs + b, color="blue")
plt.xlabel("Date of Publication")
plt.ylabel("Length of First Sentence in Characters")
plt.savefig("top-10.svg")
return flask.Response(open("top-10.svg").read(), mimetype='image/svg+xml')
@app.errorhandler(404)
def page_not_found(e):
return "<h1>404</h1><p>The resource could not be found.</p>", 404
@app.route('/api/v1/resources/books', methods=['GET'])
def api_filter():
query_parameters = request.args
id = query_parameters.get('id')
published = query_parameters.get('published')
author = query_parameters.get('author')
query = "SELECT * FROM books WHERE"
to_filter = []
if id:
query += ' id=? AND'
to_filter.append(id)
if published:
query += ' published=? AND'
to_filter.append(published)
if author:
query += ' author=? AND'
to_filter.append(author)
if not (id or published or author):
return page_not_found(404)
query = query[:-4] + ';'
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
results = cur.execute(query, to_filter).fetchall()
return jsonify(results)
app.run(host="0.0.0.0")
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (https://matplotlib.org/) -->
<svg height="288pt" version="1.1" viewBox="0 0 432 288" width="432pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 288
L 432 288
L 432 0
L 0 0
z
" style="fill:#ffffff;"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 54 252
L 388.8 252
L 388.8 34.56
L 54 34.56
z
" style="fill:#ffffff;"/>
</g>
<g id="PathCollection_1">
<defs>
<path d="M 0 3
C 0.795609 3 1.55874 2.683901 2.12132 2.12132
C 2.683901 1.55874 3 0.795609 3 0
C 3 -0.795609 2.683901 -1.55874 2.12132 -2.12132
C 1.55874 -2.683901 0.795609 -3 0 -3
C -0.795609 -3 -1.55874 -2.683901 -2.12132 -2.12132
C -2.683901 -1.55874 -3 -0.795609 -3 0
C -3 0.795609 -2.683901 1.55874 -2.12132 2.12132
C -1.55874 2.683901 -0.795609 3 0 3
z
" id="m47e5041dd1" style="stroke:#ff0000;"/>
</defs>
<g clip-path="url(#p518ec59fa4)">
<use style="fill:#ff0000;stroke:#ff0000;" x="373.545463" xlink:href="#m47e5041dd1" y="170.377278"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="369.488251" xlink:href="#m47e5041dd1" y="44.454632"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="365.431039" xlink:href="#m47e5041dd1" y="183.128939"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="361.373826" xlink:href="#m47e5041dd1" y="159.219575"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="357.316614" xlink:href="#m47e5041dd1" y="218.992983"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="357.316614" xlink:href="#m47e5041dd1" y="207.038302"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="353.259401" xlink:href="#m47e5041dd1" y="199.068514"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="349.202189" xlink:href="#m47e5041dd1" y="141.686042"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="345.144977" xlink:href="#m47e5041dd1" y="150.452809"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="341.087764" xlink:href="#m47e5041dd1" y="156.03166"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="337.030552" xlink:href="#m47e5041dd1" y="191.895705"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="332.97334" xlink:href="#m47e5041dd1" y="89.882422"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="328.916127" xlink:href="#m47e5041dd1" y="224.571835"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="324.858915" xlink:href="#m47e5041dd1" y="215.805068"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="320.801703" xlink:href="#m47e5041dd1" y="130.52834"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="316.74449" xlink:href="#m47e5041dd1" y="180.738002"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="312.687278" xlink:href="#m47e5041dd1" y="169.580299"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="308.630066" xlink:href="#m47e5041dd1" y="167.986342"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="304.572853" xlink:href="#m47e5041dd1" y="195.880599"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="300.515641" xlink:href="#m47e5041dd1" y="144.076979"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="296.458429" xlink:href="#m47e5041dd1" y="84.303571"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="292.401216" xlink:href="#m47e5041dd1" y="97.055231"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="288.344004" xlink:href="#m47e5041dd1" y="218.196005"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="288.344004" xlink:href="#m47e5041dd1" y="176.753108"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="284.286791" xlink:href="#m47e5041dd1" y="236.526516"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="280.229579" xlink:href="#m47e5041dd1" y="197.474557"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="276.172367" xlink:href="#m47e5041dd1" y="122.558552"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="272.115154" xlink:href="#m47e5041dd1" y="71.55191"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="268.057942" xlink:href="#m47e5041dd1" y="148.858851"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="264.00073" xlink:href="#m47e5041dd1" y="184.722896"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="259.943517" xlink:href="#m47e5041dd1" y="125.746467"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="255.886305" xlink:href="#m47e5041dd1" y="185.519875"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="251.829093" xlink:href="#m47e5041dd1" y="187.113832"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="247.77188" xlink:href="#m47e5041dd1" y="121.761573"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="243.714668" xlink:href="#m47e5041dd1" y="87.491486"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="239.657456" xlink:href="#m47e5041dd1" y="146.467915"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="235.600243" xlink:href="#m47e5041dd1" y="209.429238"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="231.543031" xlink:href="#m47e5041dd1" y="222.180898"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="227.485819" xlink:href="#m47e5041dd1" y="198.271535"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="223.428606" xlink:href="#m47e5041dd1" y="148.061873"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="219.371394" xlink:href="#m47e5041dd1" y="193.489663"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="215.314181" xlink:href="#m47e5041dd1" y="232.541623"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="211.256969" xlink:href="#m47e5041dd1" y="214.211111"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="207.199757" xlink:href="#m47e5041dd1" y="207.038302"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="203.142544" xlink:href="#m47e5041dd1" y="113.791785"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="199.085332" xlink:href="#m47e5041dd1" y="153.640724"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="195.02812" xlink:href="#m47e5041dd1" y="144.076979"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="190.970907" xlink:href="#m47e5041dd1" y="232.541623"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="186.913695" xlink:href="#m47e5041dd1" y="89.882422"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="182.856483" xlink:href="#m47e5041dd1" y="107.415955"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="178.79927" xlink:href="#m47e5041dd1" y="206.241323"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="178.79927" xlink:href="#m47e5041dd1" y="105.025019"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="174.742058" xlink:href="#m47e5041dd1" y="99.446168"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="170.684846" xlink:href="#m47e5041dd1" y="226.962771"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="166.627633" xlink:href="#m47e5041dd1" y="195.880599"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="162.570421" xlink:href="#m47e5041dd1" y="187.113832"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="158.513209" xlink:href="#m47e5041dd1" y="88.288465"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="154.455996" xlink:href="#m47e5041dd1" y="215.805068"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="150.398784" xlink:href="#m47e5041dd1" y="227.75975"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="146.341571" xlink:href="#m47e5041dd1" y="226.165792"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="138.227147" xlink:href="#m47e5041dd1" y="173.565193"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="134.169934" xlink:href="#m47e5041dd1" y="125.746467"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="130.112722" xlink:href="#m47e5041dd1" y="225.368814"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="126.05551" xlink:href="#m47e5041dd1" y="238.120474"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="117.941085" xlink:href="#m47e5041dd1" y="181.534981"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="97.655023" xlink:href="#m47e5041dd1" y="100.243146"/>
<use style="fill:#ff0000;stroke:#ff0000;" x="69.254537" xlink:href="#m47e5041dd1" y="242.105368"/>
</g>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path d="M 0 0
L 0 3.5
" id="m21e12e9cfc" style="stroke:#000000;stroke-width:0.8;"/>
</defs>
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="73.311749" xlink:href="#m21e12e9cfc" y="252"/>
</g>
</g>
<g id="text_1">
<!-- 1940 -->
<defs>
<path d="M 12.40625 8.296875
L 28.515625 8.296875
L 28.515625 63.921875
L 10.984375 60.40625
L 10.984375 69.390625
L 28.421875 72.90625
L 38.28125 72.90625
L 38.28125 8.296875
L 54.390625 8.296875
L 54.390625 0
L 12.40625 0
z
" id="DejaVuSans-49"/>
<path d="M 10.984375 1.515625
L 10.984375 10.5
Q 14.703125 8.734375 18.5 7.8125
Q 22.3125 6.890625 25.984375 6.890625
Q 35.75 6.890625 40.890625 13.453125
Q 46.046875 20.015625 46.78125 33.40625
Q 43.953125 29.203125 39.59375 26.953125
Q 35.25 24.703125 29.984375 24.703125
Q 19.046875 24.703125 12.671875 31.3125
Q 6.296875 37.9375 6.296875 49.421875
Q 6.296875 60.640625 12.9375 67.421875
Q 19.578125 74.21875 30.609375 74.21875
Q 43.265625 74.21875 49.921875 64.515625
Q 56.59375 54.828125 56.59375 36.375
Q 56.59375 19.140625 48.40625 8.859375
Q 40.234375 -1.421875 26.421875 -1.421875
Q 22.703125 -1.421875 18.890625 -0.6875
Q 15.09375 0.046875 10.984375 1.515625
z
M 30.609375 32.421875
Q 37.25 32.421875 41.125 36.953125
Q 45.015625 41.5 45.015625 49.421875
Q 45.015625 57.28125 41.125 61.84375
Q 37.25 66.40625 30.609375 66.40625
Q 23.96875 66.40625 20.09375 61.84375
Q 16.21875 57.28125 16.21875 49.421875
Q 16.21875 41.5 20.09375 36.953125
Q 23.96875 32.421875 30.609375 32.421875
z
" id="DejaVuSans-57"/>
<path d="M 37.796875 64.3125
L 12.890625 25.390625
L 37.796875 25.390625
z
M 35.203125 72.90625
L 47.609375 72.90625
L 47.609375 25.390625
L 58.015625 25.390625
L 58.015625 17.1875
L 47.609375 17.1875
L 47.609375 0
L 37.796875 0
L 37.796875 17.1875
L 4.890625 17.1875
L 4.890625 26.703125
z
" id="DejaVuSans-52"/>
<path d="M 31.78125 66.40625
Q 24.171875 66.40625 20.328125 58.90625
Q 16.5 51.421875 16.5 36.375
Q 16.5 21.390625 20.328125 13.890625
Q 24.171875 6.390625 31.78125 6.390625
Q 39.453125 6.390625 43.28125 13.890625
Q 47.125 21.390625 47.125 36.375
Q 47.125 51.421875 43.28125 58.90625
Q 39.453125 66.40625 31.78125 66.40625
z
M 31.78125 74.21875
Q 44.046875 74.21875 50.515625 64.515625
Q 56.984375 54.828125 56.984375 36.375
Q 56.984375 17.96875 50.515625 8.265625
Q 44.046875 -1.421875 31.78125 -1.421875
Q 19.53125 -1.421875 13.0625 8.265625
Q 6.59375 17.96875 6.59375 36.375
Q 6.59375 54.828125 13.0625 64.515625
Q 19.53125 74.21875 31.78125 74.21875
z
" id="DejaVuSans-48"/>
</defs>
<g transform="translate(60.586749 266.598437)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-49"/>
<use x="63.623047" xlink:href="#DejaVuSans-57"/>
<use x="127.246094" xlink:href="#DejaVuSans-52"/>
<use x="190.869141" xlink:href="#DejaVuSans-48"/>
</g>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="113.883873" xlink:href="#m21e12e9cfc" y="252"/>
</g>
</g>
<g id="text_2">
<!-- 1950 -->
<defs>
<path d="M 10.796875 72.90625
L 49.515625 72.90625
L 49.515625 64.59375
L 19.828125 64.59375
L 19.828125 46.734375
Q 21.96875 47.46875 24.109375 47.828125
Q 26.265625 48.1875 28.421875 48.1875
Q 40.625 48.1875 47.75 41.5
Q 54.890625 34.8125 54.890625 23.390625
Q 54.890625 11.625 47.5625 5.09375
Q 40.234375 -1.421875 26.90625 -1.421875