🎀 ( •̀ ω •́ )✧ 우당탕탕 streamlit 사용 일지 ! ( 2022-12-30 ~ 2023-01-21)
결과물 보러가기 ( ~2023-01-29 임시 공개 )
💟 당신이 선호하는 방, 호빵
NEED TO CHECK
- python 3.9
- streamlit 1.16
- streamlit_folium 0.8.1
- pyproj
- 좌표 변환을 위함
python 3.8 버전과 streamlit 1.1x version 을 사용하면 오류가 발생한다.
요구 사항 정리
- foilum 지도 띄우기
- 동그란 마커 띄우기
- 동그란 마커 클릭시 오른쪽 sidebar open
- 상단 메뉴바에 시/군/구 선택
- 시/군/구 별로 geometry 표시 되어야 할지 확인하기
- 선택시 중심으로 이동
실행 이미지
구현 코드
import folium
import streamlit as st
from streamlit_folium import st_folium
center = [37.5010,127.0509]
# center on Seoul pharmacy
m = folium.Map(location=center, zoom_start=18)
markers = plugins.MarkerCluster(transformed_coord_list)
markers.add_to(m)
# call to render Folium map in Streamlit
st_data = st_folium(m, width=725)
🎀 ( •̀ ω •́ )✧ Marker Click Event 를 먼저 처리해보자 !
🎀 ( •̀ ω •́ )✧ 그냥 onClick Event 를 추가하면 되는데 뭐가 문제냐 ! 라고 물어보신다면 다음과 같은 문제가 있다.
- streamlit folium 의 Marker 는 click event 를 지원하지 않았다.
- folium.Element 로 script 를 추가하는 것이 동작하지 않았다.
실행되지 않는 코드
click_js = """function onClick(e) {
var point = e.latlng; alert(point)
}"""
e = folium.Element(click_js)
html = m.get_root()
html.script.get_root().render()
html.script._children[e.get_name()] = e
🎀 ( •̀ ω •́ )✧ Marker 의 template 에 들어가는 javascript 를 변경하면 click event 를 추가할 수 있다.
실행 이미지
구현 코드
import folium
from jinja2 import Template
from folium.map import Marker
import streamlit
from streamlit_folium import st_folium
# Modify Marker template to include the onClick event
click_template = """{% macro script(this, kwargs) %}
function onClick(e) {
let point = e.latlng; alert(point)
}
var {{ this.get_name() }} = L.marker(
{{ this.location|tojson }},
{{ this.options|tojson }}
).addTo({{ this._parent.get_name() }}).on('click', onClick);
{% endmacro %}"""
# Change template to custom template
Marker._template = Template(click_template)
location_center = [51.7678, -0.00675564]
m = folium.Map(location_center, zoom_start=13)
e = folium.Element(click_js)
html = m.get_root()
html.script.get_root().render()
html.script._children[e.get_name()] = e
#Add marker (click on map an alert will display with latlng values)
marker = folium.Marker(location_center).add_to(m)
st_folium(m, width=750)
선택된 구 정보를 활용해서 Backend 에 매물 정보를 요청한다.
selected_gu = st.selectbox(
label = "구",
options = GU_INFO,
label_visibility=st.session_state.visibility,
disabled=st.session_state.disabled,
index = selected_idx
)
st.session_state['center']['coord'] = [GU_INFO_CENTER[selected_gu]["lat"],GU_INFO_CENTER[selected_gu]["lng"]]
url = ''.join([BACKEND_ADDRESS, DOMAIN_INFO['map'], DOMAIN_INFO['items']])
res = requests.post(url,data=json.dumps(user_info) )
st.session_state['item_list'] = [*res.json()['houses'].values()]
선택시 반환되는 좌표 정보를 map 의 location 에 넣어주면 된다.
class folium.folium.Map(location=None, width='100%', height='100%', left='0%', top='0%', position='relative', tiles='OpenStreetMap', attr=None, min_zoom=0, max_zoom=18, zoom_start=10, min_lat=- 90, max_lat=90, min_lon=- 180, max_lon=180, max_bounds=False, crs='EPSG3857', control_scale=False, prefer_canvas=False, no_touch=False, disable_3d=False, png_enabled=False, zoom_control=True, **kwargs)
Link 가 걸린 element 의 id 를 가져오는 멋찐 기능..
jsx, typescript 로 멋찐 걸 만들어 낼 수 있다니
공부 욕구가 뿜뿜했다.
import streamlit as st
from st_click_detector import click_detector
content = """<p><a href='#' id='Link 1'>First link</a></p>
<p><a href='#' id='Link 2'>Second link</a></p>
<a href='#' id='Image 1'><img width='20%' src='https://images.unsplash.com/photo-1565130838609-c3a86655db61?w=200'></a>
<a href='#' id='Image 2'><img width='20%' src='https://images.unsplash.com/photo-1565372195458-9de0b320ef04?w=200'></a>
"""
clicked = click_detector(content)
st.markdown(f"**{clicked} clicked**" if clicked != "" else "**No click**")
비록 우리는 if else 로 처리했으나... 언젠가 개선할 날을 위해 소스를 남긴다.
def switch_page(page_name: str):
from streamlit import _RerunData, _RerunException
from streamlit.source_util import get_pages
def standardize_name(name: str) -> str:
return name.lower().replace("_", " ")
page_name = standardize_name(page_name)
pages = get_pages("streamlit_app.py") # OR whatever your main page is called
for page_hash, config in pages.items():
if standardize_name(config["page_name"]) == page_name:
raise _RerunException(
_RerunData(
page_script_hash=page_hash,
page_name=page_name,
)
)
page_names = [standardize_name(config["page_name"]) for config in pages.values()]
raise ValueError(f"Could not find page {page_name}. Must be one of {page_names}")```
🎀 ( •̀ ω •́ )✧ 모든게 준비됐다 ! 잘 조합해서 만들면 된다.
🎀 ( •̀ ω •́ )✧ 그런데..! 엄청난 산이 남아 있었으니....
📑 바야흐로 링크 공개 하루 전 , 팀원들과 열심히 기능 테스트를 진행하는데....
👩 : 누가 내 아이디로 로그인 했어 ? 나 막 내가 선택 안한 지역 구로 가는데!?
😸 : 어머 ! 난가봐, 아까 빌려 했는데 로그아웃을 안했네 ! 깔깔깔??? 생각해보면 살짝 말이 안된다.. Session 이란게 뭔가.
컴퓨터와 사용자 간의 연결..! 하다못해 새로운 탭도 새로운 세션으로 쳐질텐데 !?
하지만 그 땐 발견하지 못하고..📑 링크 공개 직전, 다섯 명의 유저가 유입되었다고 생각하고 마구 마구 interaction 을 만드는데..!!
🙄 단체로 동기화가 된다. 하하. 웬걸... session state 가 공유되는 것이다. 말도 안돼..
📢 주의 ! 해당 문제는 미해결 상태입니다. 추측성 발언이 담겨 있습니다.
A 유저 👩 가 서초구를 선택하여 매물을 보고 있던 상황
B 유저 😸 가 강남구의 매물을 보기 위해 구를 변경
👩 와 😸 모두 강남구에 있게 된 상황..!
❌ 1 ) 로그인 후 지도 페이지 진입할 때 session_state 에 존재하는 key 지우고 새로 설정
❌ 2 ) streamlit version 을 upgrade ( 1.16 1.17 )
❌ 3 ) streamlit version 을 downgrade ( 1.16 1.9 )
⭕ 4 ) streamlit version 을 downgrade 하고 SessionState.py 파일 추가
📢 주의 ! 해당 문제는 미해결 상태입니다. 추측성 발언이 담겨 있습니다.
Fact Check
- Streamlit 의 Session State 는 서로 다른 유저 간의 독립적인 session state 를 제공한다.
- Reference : Streamlit SessionState 동영상 44초
Streamlit 의 Session State 는 in-memory 에 저장된다.
~~📢 추측 ) 사용자들이 공유 네트워크 환경에서 애플리케이션을 사용하거나 애플리케이션이 shared hosting service 나 cloud-based platform 같은 multi-user 환경에서 동작하는 경우 session state 를 공유하는 듯하다.
📢 추측 ) 해결하려면 unique session key, cookies, 또는 각 user 의 session 을 확인할 수 있는 token 을 사용하는 방법이 있다. ~~
Streamlit 에서 session 으로 저장되지 않은 데이터들은 유저 간 공유된다.
KEYWORKD
: NAS, SAN, load balancer, session key
Session Object 를 가져와 유저 별로 session state 가질 수 있도록 한다.
한 줄 평 : 빠르지만 제약이 좀 있는 녀석
💖 1 ) 개발 속도가 빠르다. Python 덕분인지, layout 에 대한 고민을 할 필요가 없어서 인지, 간단한 웹 페이지 구성하기 정말 좋다고 느꼈다.
💖 2 ) 빠른 개발 속도 덕분에 모델링, 분석에 집중할 시간이 늘어난다. 내가 분석한 내용을 간단히 보여줄 일이 있을 때 좋을 것 같다고 생각했다.
💖 3 ) 사람들이 React 와 Typescript 로 다양한 기능을 만들어두었다. 내가 원하는 것을 직접 만들어 사용할 수 있음이 매력적으로 다가왔다.
💔 1 ) 편리한 만큼 제약사항이 있다. Style 적용이나, Streamlit 과 Web 의 상호작용이 단방향이기 때문에 오는 제약 등이 있다.
💔 2 ) Session State 공유의 건..ㅎ ( streamlit 단점이라고 할 수 있을진 모르겠지만.. )
Demo 용 웹을 만드는 툴 ! 로 배운 뒤 꽤나 오랜 시간을 걸쳐 베타 버전 웹 페이지를 공개했다.
말 그대로 간단한 웹을 만드는 도구인데 시간을 너무 많이 쓰고 있는건지 걱정이 됐다.
제약사항을 발견할 때 마다 아, 리액트할걸
하는 생각도 종종 들었다.
물론 리액트 쓴다고 더 빨리 한다거나 더 멋졌을 거란 말은 아니다.
그래도 문법이 단순하고 layout 잡기가 쉬워서 꽤나 빠르게 백엔드와 모델의 결과를 확인하기 좋았다.
앞으로도 분석 결과를 시뮬레이션 해서 볼 일이 있다면 streamlit 을 애용할 것 같다.
하트를 누르면 빨간색으로 변한다 ! 한 번 더 누르면 원래대로 되돌릴 수도 있다.
마음에 들었는데 깜빡임 문제 때문에 아쉽지만 이모지로 바꾸었다.
#heart{
font-size: 25px;
}
#heart:hover{
color:red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://kit.fontawesome.com/3929e16ef5.js" crossorigin="anonymous"></script>
<script src="{% static 'network/functions.js' %}"></script>
</head>
<body>
<div>
<i id="heart" class="far fa-heart"></i>
</div>
</body>
</html