mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-04-22 10:28:02 +00:00
211 lines
6.1 KiB
Python
211 lines
6.1 KiB
Python
# Copyright 2018-2022 Streamlit Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""A wrapper for simple PyDeck scatter charts."""
|
|
|
|
import copy
|
|
import json
|
|
from typing import Any, Dict
|
|
from typing import cast
|
|
|
|
import pandas as pd
|
|
|
|
import streamlit
|
|
import streamlit.elements.deck_gl_json_chart as deck_gl_json_chart
|
|
from streamlit.errors import StreamlitAPIException
|
|
from streamlit.proto.DeckGlJsonChart_pb2 import DeckGlJsonChart as DeckGlJsonChartProto
|
|
|
|
|
|
class MapMixin:
|
|
def map(self, data=None, zoom=None, use_container_width=True):
|
|
"""Display a map with points on it.
|
|
|
|
This is a wrapper around st.pydeck_chart to quickly create scatterplot
|
|
charts on top of a map, with auto-centering and auto-zoom.
|
|
|
|
When using this command, we advise all users to use a personal Mapbox
|
|
token. This ensures the map tiles used in this chart are more
|
|
robust. You can do this with the mapbox.token config option.
|
|
|
|
To get a token for yourself, create an account at
|
|
https://mapbox.com. It's free! (for moderate usage levels). For more
|
|
info on how to set config options, see
|
|
https://docs.streamlit.io/library/advanced-features/configuration#set-configuration-options
|
|
|
|
Parameters
|
|
----------
|
|
data : pandas.DataFrame, pandas.Styler, numpy.ndarray, Iterable, dict,
|
|
or None
|
|
The data to be plotted. Must have columns called 'lat', 'lon',
|
|
'latitude', or 'longitude'.
|
|
zoom : int
|
|
Zoom level as specified in
|
|
https://wiki.openstreetmap.org/wiki/Zoom_levels
|
|
|
|
Example
|
|
-------
|
|
>>> import streamlit as st
|
|
>>> import pandas as pd
|
|
>>> import numpy as np
|
|
>>>
|
|
>>> df = pd.DataFrame(
|
|
... np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4],
|
|
... columns=['lat', 'lon'])
|
|
>>>
|
|
>>> st.map(df)
|
|
|
|
.. output::
|
|
https://share.streamlit.io/streamlit/docs/main/python/api-examples-source/charts.map.py
|
|
height: 650px
|
|
|
|
"""
|
|
map_proto = DeckGlJsonChartProto()
|
|
map_proto.json = to_deckgl_json(data, zoom)
|
|
map_proto.use_container_width = use_container_width
|
|
return self.dg._enqueue("deck_gl_json_chart", map_proto)
|
|
|
|
@property
|
|
def dg(self) -> "streamlit.delta_generator.DeltaGenerator":
|
|
"""Get our DeltaGenerator."""
|
|
return cast("streamlit.delta_generator.DeltaGenerator", self)
|
|
|
|
|
|
# Map used as the basis for st.map.
|
|
_DEFAULT_MAP = dict(deck_gl_json_chart.EMPTY_MAP) # type: Dict[str, Any]
|
|
_DEFAULT_MAP["mapStyle"] = "mapbox://styles/mapbox/light-v10"
|
|
|
|
# Other default parameters for st.map.
|
|
_DEFAULT_COLOR = [200, 30, 0, 160]
|
|
_DEFAULT_ZOOM_LEVEL = 12
|
|
_ZOOM_LEVELS = [
|
|
360,
|
|
180,
|
|
90,
|
|
45,
|
|
22.5,
|
|
11.25,
|
|
5.625,
|
|
2.813,
|
|
1.406,
|
|
0.703,
|
|
0.352,
|
|
0.176,
|
|
0.088,
|
|
0.044,
|
|
0.022,
|
|
0.011,
|
|
0.005,
|
|
0.003,
|
|
0.001,
|
|
0.0005,
|
|
0.00025,
|
|
]
|
|
|
|
|
|
def _get_zoom_level(distance):
|
|
"""Get the zoom level for a given distance in degrees.
|
|
|
|
See https://wiki.openstreetmap.org/wiki/Zoom_levels for reference.
|
|
|
|
Parameters
|
|
----------
|
|
distance : float
|
|
How many degrees of longitude should fit in the map.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
The zoom level, from 0 to 20.
|
|
|
|
"""
|
|
|
|
# For small number of points the default zoom level will be used.
|
|
if distance < _ZOOM_LEVELS[-1]:
|
|
return _DEFAULT_ZOOM_LEVEL
|
|
|
|
for i in range(len(_ZOOM_LEVELS) - 1):
|
|
if _ZOOM_LEVELS[i + 1] < distance <= _ZOOM_LEVELS[i]:
|
|
return i
|
|
|
|
|
|
def to_deckgl_json(data, zoom):
|
|
|
|
if data is None or data.empty:
|
|
return json.dumps(_DEFAULT_MAP)
|
|
|
|
if "lat" in data:
|
|
lat = "lat"
|
|
elif "latitude" in data:
|
|
lat = "latitude"
|
|
else:
|
|
raise StreamlitAPIException(
|
|
'Map data must contain a column named "latitude" or "lat".'
|
|
)
|
|
|
|
if "lon" in data:
|
|
lon = "lon"
|
|
elif "longitude" in data:
|
|
lon = "longitude"
|
|
else:
|
|
raise StreamlitAPIException(
|
|
'Map data must contain a column called "longitude" or "lon".'
|
|
)
|
|
|
|
if data[lon].isnull().values.any() or data[lat].isnull().values.any():
|
|
raise StreamlitAPIException("Latitude and longitude data must be numeric.")
|
|
|
|
data = pd.DataFrame(data)
|
|
|
|
min_lat = data[lat].min()
|
|
max_lat = data[lat].max()
|
|
min_lon = data[lon].min()
|
|
max_lon = data[lon].max()
|
|
center_lat = (max_lat + min_lat) / 2.0
|
|
center_lon = (max_lon + min_lon) / 2.0
|
|
range_lon = abs(max_lon - min_lon)
|
|
range_lat = abs(max_lat - min_lat)
|
|
|
|
if zoom == None:
|
|
if range_lon > range_lat:
|
|
longitude_distance = range_lon
|
|
else:
|
|
longitude_distance = range_lat
|
|
zoom = _get_zoom_level(longitude_distance)
|
|
|
|
# "+1" because itertuples includes the row index.
|
|
lon_col_index = data.columns.get_loc(lon) + 1
|
|
lat_col_index = data.columns.get_loc(lat) + 1
|
|
final_data = []
|
|
for row in data.itertuples():
|
|
final_data.append(
|
|
{"lon": float(row[lon_col_index]), "lat": float(row[lat_col_index])}
|
|
)
|
|
|
|
default = copy.deepcopy(_DEFAULT_MAP)
|
|
default["initialViewState"]["latitude"] = center_lat
|
|
default["initialViewState"]["longitude"] = center_lon
|
|
default["initialViewState"]["zoom"] = zoom
|
|
default["layers"] = [
|
|
{
|
|
"@@type": "ScatterplotLayer",
|
|
"getPosition": "@@=[lon, lat]",
|
|
"getRadius": 10,
|
|
"radiusScale": 10,
|
|
"radiusMinPixels": 3,
|
|
"getFillColor": _DEFAULT_COLOR,
|
|
"data": final_data,
|
|
}
|
|
]
|
|
return json.dumps(default)
|