Jakiś czas temu parę osób pytało jak się robi infografiki — takie jak w moim dawnym wpisie na blogu, gdzie między innymi można było zobaczyć różnice w zachowaniach młodzieży w różnych państwach Europy na podstawie obserwacji lokalnych znaków drogowych. Infografiki składały się zasadniczo z dwóch części — mapy i legendy. Niestety, nie dokumentowałem na bieżąco całego procesu powstawania tamtej wprawki, pełnego błędów i ślepych zaułków, więc wszystko teraz wyda się o wiele łatwiejsze niż było w rzeczywistości.
Celem opisanych tu działań jest utworzenie infografiki w postaci pliku HTML, który bez problemu powinna pokazać każda przeglądarka WWW. Do tego potrzebną są odpowiednie materiały i narzędzia. Materiały to:
Narzędzia to:
- edytor tekstu — zwykły prosty edytor tekstu, na przykład znany niektórym użytkownikom Windows Notatnik
- przeglądarka WWW
- Interpreter języka programowania Python
W Pythona trzeba zaopatrzyć się samemu jeżeli korzysta się z Windows — OSX i Linux ma go standardowo zainstalowanego. Dystrybucja Pythona, z której korzystam, to Anaconda, którą można (darmowo i legalnie) pobrać ze strony plików do pobrania Continuum Analytics. Wybierz tam swój system operacyjny i wersję Pythona — 3.5 (lub wyższą). Nie pobierz przez pomyłkę którejś wersji z serii 2.x — podane dalej przykłady nie będą poprawnie działały. Odpal instalator, parę razy kliknij gdzie trzeba i gotowe. Anaconda zawiera wszystkie biblioteki, których używam w tych przykładach, więc zaraz po zainstalowaniu będzie można zabrać się do pracy.
Ponieważ projekt będzie składał się z kilku czy nawet kilkudziesięciu plików warto sobie przygotować miejsce do pracy — czyli katalog (po windowsowemu nazywa się to „folder). Na tym etapie nie ma znaczenia jak go nazwiesz i gdzie się będzie znajdował, ale na potrzeby tego tekstu będę go nazywał „infog”. W tym katalogu znajdą się pliki, które będziemy tworzyli — plik strony w postaci HTML oraz nasze programy w Pythonie. Zadbaj też o to, by w katalogu znalazł się plik ze ściągniętą mapą.
Zaczniemy od utworzenia najprostszej możliwej strony zawierającej jedynie pobraną mapę Europy. Cały kod strony może wyglądać tak:
<html> <head> <title> Moja pierwsza infografika </title> </head> <body> <img src='mojamapa.svg'> </body> </html>
Możesz ten kod przekleić do edytora tekstu i zapisać w katalogu „infog” np. jako „wprawka.htm”, choć oczywiście masz pełne prawo do wybrania własnej nazwy. Zrób kopię „Blank_map_of_Europe_2.svg” o nazwie „mojamapa.svg” (oczywiście w katalogu „infog”). Teraz w swojej przeglądarce otwórz plik „wprawka.htm”. Jeżeli wszystko poszło dobrze to zobaczysz szarą mapę Europy. Czas zabrać się za kolorowanie.
Zacznijmy od pokolorowania jednego państwa — Polski — na zielono. Poniższy kod to wszystko, czego do tego potrzebujesz. Skopiuj go do edytora tekstu i zapisz w katalogu „infog” pod nazwą np. „koloruj.py”.
from bs4 import BeautifulSoup dcolor= '192, 192, 192' dstroke='55, 55, 55' dstroke_width = '0.5' svg = open('Blank_map_of_Europe_2.svg', 'r').read() soup = BeautifulSoup(svg, 'xml') paths = soup.find_all('path') for p in paths: acolor = '192, 192, 192' # assume default color if p['id'] == 'pl': acolor = '0, 255, 0' mstyle = 'fill: rgb(' + acolor + '); stroke: rgb(' + dstroke + '); stroke-width: '+dstroke_width+'; fill-opacity:1' p['style'] = mstyle svg = open('mojamapa.svg', 'w') svg.write(soup.prettify(formatter=None)) svg.close()
Co tu robimy? Na poczatku wczytujemy z dysku mapę (linia 7) i tłumaczymy do postaci nadającej się do dlaszej obróbki (linie 8 i 9). Tworzymy listę wszystkich obszarów na mapie i nadajemy jej nazwę „paths”. Na szczęście twórcy mapy zadbali o to, by każdy obszar był oznaczony dwuliterowym kodem państwa zgodnie z normą ISO 3166-1, co bardzo ułatwia dalsze działania. Od lini 11 przelatujemy przez wszystkie elementy listy „paths” (pojedynczy element nazywamy sobie „p”) i sprawdzamy, czy odpowiada potrzebnemu nam kodowi państwa — w tym przypadku ‚pl’. Jeżeli tak, to zamieniamy kolor podstawowy (ustawiony w linii 13) na nasz ulubiony zielony.
Aby wykonać program musisz wejść do wiersza poleceń (w Windows poleceniem „cmd”), przejść do katalogu „infog”, a następnie wpisać polecenie „python koloruj.py”. Jeżeli wszystko poszło dobrze to po przejściu do przeglądarki i odświeżeniu strony „wprawka.htm” powinna się ukazać szara mapa Europy z zieloną Polską:
Program zrobił to co miał zrobić, ale jest do kitu, i to z wielu powodów. Jeżeli by trzeba było w taki sposób pokolorować więcej niż jedno państwo trzeba by wstawiać całą masę linijek typu:
if p['id'] == 'fr': acolor = '0, 0, 255' if p['id'] == 'de': acolor = '255, 0, 0'
To by nam dało niebieską Francję i czerwone Niemcy, ale tak napuchnięty kod jest brzydki i trudno jest go modyfikować. Co prawda można go łatwo rozbudowywać metodą kopiuj-wklej, ale wtedy coraz łatwiej o pomyłki, których szukanie może każdego zniechęcić do tej zabawy.
Warto też pamiętać o jednym: mamy całą masę państw do pokolorowania na zaledwie trzy kolory (dzieci idą, dzieci biegną, dzieci pędzą). Tak czy tak musimy stworzyć listy państw w danej kategorii:
children_walk = ['at', 'be', 'cz', 'dk', 'gr', 'ie', 'no', 'sk', 'se', 'tr'] children_run = ['fr', 'de', 'hu', 'is', 'hu', 'nl', 'pl', 'ch', 'uk'] children_dash = ['ee', 'it', 'ru', 'by', 'es', 'ua']
Jeżeli w przyszłości dajmy na to Francja przyspieszy swoje dzieci to wystarczy usunąć ‚fr’ z listy children_walk i dopisać ją do children_run.
Teraz sprawdzamy już nie to, czy ‚id’ jest równe kodowi państwa lecz to, czy jest w danej liście państw. Jeżeli jest to kolorujemy, jeżeli jie to idziemy dalej. Klasycznie postąpilibyśmy teraz tak:
if p['id'] in children_walk: acolor = '0, 255, 0' #zielony elif p['id'] in children_run: acolor = '255, 255, 0' #żółty elif p['id'] in children_dash: acolor = '255, 0, 0' #czerwony
To już o tyle lepsze, że przesunięcie państwa z jednej grupy do drugiej nie będzie wymagało grzebania w kodzie przypisującym kolory, ale i tak nie wyglada to dobrze. Ogólna zasada mówi, że jeżeli linie kodu wyglądają bardzo podobnie to da sie je zastapić jakąś prostszą konstrukcją, a prosty kod łatwiej jest rozwijać i zmieniać. Poza tym ograniczamy się teraz do trzech różnych kategorii, a nie wiadomo kiedy będzie nam potrzeba ich więcej lub mniej. Warto więc rozdzielić ustawianie kolorów państw od kolorowania mapy.
W tym celu utworzymy jeden centralny słownik, początkowo pusty, który nazwiemy „colors”. W miarę potrzeb bedziemy go wypełniali kolorami, a na koniec wykorzystamy do pokolorowania mapy:
from bs4 import BeautifulSoup dcolor= '192, 192, 192' dstroke='55, 55, 55' dstroke_width = '0.5' colors = {} children_walk = ['at', 'be', 'cz', 'dk', 'gr', 'ie', 'no', 'sk', 'se', 'tr'] children_run = ['fr', 'de', 'hu', 'is', 'hu', 'nl', 'pl', 'ch', 'uk'] children_dash = ['ee', 'it', 'ru', 'by', 'es', 'ua'] for c in children_walk: colors[c] = '0, 255, 0' #zielony for c in children_run: colors[c] = '255, 255, 0' #żółty for c in children_dash: colors[c] = '255, 0, 0' #czerwony svg = open('Blank_map_of_Europe_2.svg', 'r').read() soup = BeautifulSoup(svg, 'xml') paths = soup.find_all('path') for p in paths: acolor = '192, 192, 192' # assume default color if p['id'] in colors: acolor = colors[p['id']] mstyle = 'fill: rgb(' + acolor + '); stroke: rgb(' + dstroke + '); stroke-width: '+dstroke_width+'; fill-opacity:1' p['style'] = mstyle svg = open('mojamapa.svg', 'w') svg.write(soup.prettify(formatter=None)) svg.close()
Od tej chwili nie musimy już za bardzo grzebać w pętli kolorującej (cały wcięty obszar kodu począwszy od „for”), cała zabawa z ustawianiem kolorów zostaje na początku programu. Przy okazji pisania tego tekstu odkryłem, że przegapiłem Finlandię, Portugalię i Rumunię. Teraz wystarczy je dopisać do odpowiedniej listy. Zamiast
children_walk = ['at', 'be', 'cz', 'dk', 'gr', 'ie', 'no', 'sk', 'se', 'tr'] children_run = ['fr', 'de', 'hu', 'is', 'hu', 'nl', 'pl', 'ch', 'uk'] children_dash = ['ee', 'it', 'ru', 'by', 'es', 'ua']
będziemy mieli
children_walk = ['at', 'be', 'cz', 'dk', 'gr', 'ie', 'no', 'sk', 'se', 'tr', 'fi'] children_run = ['fr', 'de', 'hu', 'is', 'hu', 'nl', 'pl', 'ch', 'uk'] children_dash = ['ee', 'it', 'ru', 'by', 'es', 'ua', 'pt', 'ro']
Lista na Wikipedii nie uwzględnia Litwy i Łotwy — zapewne są takie same jak w innych byłych republikach ZSRR, ale nie sprawdzałem więc państwa te pozostają szare.
Ponowne odpalenie „koloruj.py” i odświeżenie strony da nam taki efekt:
Wygląda to już dość dobrze, ale są pewne oczywiste baboki. Chociaż wpisaliśmy dane dla Rosji (‚ru’) i Zjednoczonego Królestwa (‚gb’), oba te państwa pzostały szare. Dlaczego?
W tej sytuacji warto zorientować się, jakie zawarte w mapie obszary nie zostały pokolorowane. Skoro przelatując przez obszary wyszukujemy ich ‚id’ w słowniku ‚colors’ warto kazać programowi informować nas, że dany obszar nie został znaleziony:
if p['id'] in colors: acolor = colors[p['id']] elif: print(p['id'])
Te informacje pojawią się w oknie wiersza poleceń i będą wyglądały mniej więcej tak:
Kilka pozycji od razu rzuca się w oczy — ‚ru-main’, ‚ru-kgd’, ‚gb-nir’, ‚gb-gbn’ oraz ‚large_masses_of_water’. Oczywiście ‚ru-main’ to główny obszar Rosji, a ‚ru-kgd’ to Obwód Kaliningradzki; Wielka Brytania zaś podzielona jest na część główną oraz Irlandię Północną. Podstawowy problem z Wielką Brytanią polega na tym, że standardowo nazywa się „Zjednoczonym Królestwem”, którego kodu użyliśmy w programie. Najprościej byłoby podmienić na liście ‚uk’ na ‚gb’, ale taki zabieg to samo zło — nasze oryginalne dane zostaną przekłamane. Zawsze warto jest mieć osobne miejsce na poprawne dane, a osobne na wyjątki. Tu rozwiązaniem jest dodanie nowej pozycji ‚gb’ do słownika ‚colors’ i nadanie jej wartości z ‚uk’, najlepiej zaraz po zdefiniowanych listach kolorów (a na pewno nie przed, bo wtedy ‚uk’ będzie jeszcze niezdefiniowane):
colors['gb'] = colors['uk']
To jednak nie rozwiązuje problemu kodów dłuższych niż dwuliterowe. Praktycznym rozwiązaniem będzie sprawdzanie nie całego ‚id’, ale tylko jego pierwszych dwóch znaków. Zamiast
if p['id'] in colors: acolor = colors[p['id']]
napiszemy
if p['id'][:2] in colors: acolor = colors[p['id'][:2]] else: print(p['id'])
To powtórzenie ‚p[‚id’][:2]’ też jest dość brzydkie więc dla jasności jeszcze zmodyfikujemy kod tak:
country_id = p['id'][:2] if country_id in colors: acolor = colors[country_id] else: print(p['id'])
Celowo po ‚else’ nie wykorzystałem ‚country_id’, bo zawiera ono tylko pierwsze dwa znaki, a przy odpluskwianiu interesuje nas maksimum informacji.
Właściwie kolorowanie mapy jest już zakończone. Został jeszcze jeden wkurzający drobiazg, owe „large_masses_of_water”, czyli co większe jeziora, których na porządnym kartogramie nie wypada przedstawiać. Po prostu wywalmy je (w liniach 41-42 poniższego kodu). Ostateczny kod (przynajmniej na chwilę – jedyny ostateczny kod to martwy kod) wygląda tak:
from bs4 import BeautifulSoup dcolor= '192, 192, 192' dstroke='55, 55, 55' dstroke_width = '0.5' colors = {} children_walk = ['at', 'be', 'cz', 'dk', 'gr', 'ie', 'no', 'sk', 'se', 'tr', 'fi'] children_run = ['fr', 'de', 'hu', 'is', 'hu', 'nl', 'pl', 'ch', 'uk'] children_dash = ['ee', 'it', 'ru', 'by', 'es', 'ua', 'pt', 'ro'] for c in children_walk: colors[c] = '0, 255, 0' #zielony for c in children_run: colors[c] = '255, 255, 0' #żółty for c in children_dash: colors[c] = '255, 0, 0' #czerwony colors['gb'] = colors['uk'] svg = open('Blank_map_of_Europe_2.svg', 'r').read() soup = BeautifulSoup(svg, 'xml') paths = soup.find_all('path') for p in paths: acolor = '192, 192, 192' # assume default color country_id = p['id'][:2] if country_id in colors: acolor = colors[country_id] else: print(p['id']) mstyle = 'fill: rgb(' + acolor + '); stroke: rgb(' + dstroke + '); stroke-width: '+dstroke_width+'; fill-opacity:1' p['style'] = mstyle if p['id'] == 'large_masses_of_water': p.decompose() svg = open('mojamapa.svg', 'w') svg.write(soup.prettify(formatter=None)) svg.close()
Mamy już mapę, ale strona nie zawiera jeszcze nic poza mapą, więc wróćmy do pliku „wprawka.htm”. Na początek dodajmy tytuł:
<html> <head> <title> Moja pierwsza infografika </title> </head> <body> <h1>Prędkości dzeci na przejściach dla pieszych</h1> <img src='mojamapa.svg'> </body> </html>
Jest duże prawdopodobieństwo, że zobaczysz coś takiego:
Pomimo tego, że to Polacy wymyślili Internet i WWW jakieś 150 lat temu, niektóre przeglądarki nie radzą sobie dobrze z pokazywaniem znaków innych niż z zestawu anglosaskiego, i trzeba je poinstruować jak mają sobie z tym poradzić. Dodaj w sekcji ‚head’ jedną linijkę informującą o sposobie kodowania, a wszystko będzie OK.
<html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title> Moja pierwsza infografika </title> </head> <body> <h1>Prędkości dzeci na przejściach dla pieszych</h1> <img src='mojamapa.svg' width='1000'> </body> </html>
Teraz czas zabrać się za legendę mapy i przedstawienie wszystkich znaków, na wypadek ewentualnych sporów czy w danym państwie dzieci jeszcze tylko szybko idą czy już biegną. Oraz analizę gender owych znaków — taką jak w omawianym wpisie. Oraz liczenie spadających kamieni. Ale to wszystko mnie tak wyczerpało, że spadam. Jak kamień. Ciąg dalszy przy najbliższej okazji.
Masz pytania? Uwagi? Problemy z podanym kodem? Nie potrafisz skopiować? Zapisac pliku na dysku? Otworzyć pliku w przeglądarce? Pytaj. Nie ma głupich pytań. Kontaktuj się w komentarzach po tym wpisem albo na moim Twitterze.
Czwartek, 8 Gru 2016 o 00:14 |
Cześć, Dzięki za tekst o infografice, przeczytam w weekend, teraz mam urwanie głowy, pisz, pisz. Pozdrawiam i do pisania.
Piątek, 2 Czer 2023 o 04:47 |
How Do I Stop Thinking About Someone
Prosta infografika | Szescstopni
Wtorek, 26 Wrz 2023 o 04:17 |
파워볼
Prosta infografika | Szescstopni