lets-plot 4.8.1rc1__cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lets_plot/__init__.py +382 -0
- lets_plot/_global_settings.py +192 -0
- lets_plot/_kbridge.py +197 -0
- lets_plot/_type_utils.py +133 -0
- lets_plot/_version.py +6 -0
- lets_plot/bistro/__init__.py +16 -0
- lets_plot/bistro/_plot2d_common.py +106 -0
- lets_plot/bistro/corr.py +448 -0
- lets_plot/bistro/im.py +196 -0
- lets_plot/bistro/joint.py +192 -0
- lets_plot/bistro/qq.py +207 -0
- lets_plot/bistro/residual.py +341 -0
- lets_plot/bistro/waterfall.py +332 -0
- lets_plot/export/__init__.py +6 -0
- lets_plot/export/ggsave_.py +172 -0
- lets_plot/frontend_context/__init__.py +8 -0
- lets_plot/frontend_context/_configuration.py +140 -0
- lets_plot/frontend_context/_dynamic_configure_html.py +115 -0
- lets_plot/frontend_context/_frontend_ctx.py +16 -0
- lets_plot/frontend_context/_html_contexts.py +223 -0
- lets_plot/frontend_context/_intellij_python_json_ctx.py +38 -0
- lets_plot/frontend_context/_isolated_webview_panel_ctx.py +81 -0
- lets_plot/frontend_context/_json_contexts.py +39 -0
- lets_plot/frontend_context/_jupyter_notebook_ctx.py +82 -0
- lets_plot/frontend_context/_mime_types.py +7 -0
- lets_plot/frontend_context/_static_html_page_ctx.py +76 -0
- lets_plot/frontend_context/_static_svg_ctx.py +26 -0
- lets_plot/frontend_context/_webbr_html_page_ctx.py +29 -0
- lets_plot/frontend_context/sandbox.py +5 -0
- lets_plot/geo_data/__init__.py +19 -0
- lets_plot/geo_data/core.py +335 -0
- lets_plot/geo_data/geocoder.py +988 -0
- lets_plot/geo_data/geocodes.py +512 -0
- lets_plot/geo_data/gis/__init__.py +0 -0
- lets_plot/geo_data/gis/fluent_dict.py +201 -0
- lets_plot/geo_data/gis/geocoding_service.py +42 -0
- lets_plot/geo_data/gis/geometry.py +91 -0
- lets_plot/geo_data/gis/json_request.py +232 -0
- lets_plot/geo_data/gis/json_response.py +308 -0
- lets_plot/geo_data/gis/request.py +492 -0
- lets_plot/geo_data/gis/response.py +247 -0
- lets_plot/geo_data/livemap_helper.py +65 -0
- lets_plot/geo_data/to_geo_data_frame.py +141 -0
- lets_plot/geo_data/type_assertion.py +34 -0
- lets_plot/geo_data_internals/__init__.py +4 -0
- lets_plot/geo_data_internals/constants.py +13 -0
- lets_plot/geo_data_internals/utils.py +33 -0
- lets_plot/mapping.py +115 -0
- lets_plot/package_data/lets-plot.min.js +3 -0
- lets_plot/plot/__init__.py +64 -0
- lets_plot/plot/_global_theme.py +14 -0
- lets_plot/plot/annotation.py +290 -0
- lets_plot/plot/coord.py +242 -0
- lets_plot/plot/core.py +1071 -0
- lets_plot/plot/expand_limits_.py +78 -0
- lets_plot/plot/facet.py +210 -0
- lets_plot/plot/font_features.py +71 -0
- lets_plot/plot/geom.py +9146 -0
- lets_plot/plot/geom_extras.py +53 -0
- lets_plot/plot/geom_function_.py +219 -0
- lets_plot/plot/geom_imshow_.py +393 -0
- lets_plot/plot/geom_livemap_.py +343 -0
- lets_plot/plot/ggbunch_.py +96 -0
- lets_plot/plot/gggrid_.py +139 -0
- lets_plot/plot/ggtb_.py +81 -0
- lets_plot/plot/guide.py +231 -0
- lets_plot/plot/label.py +187 -0
- lets_plot/plot/marginal_layer.py +181 -0
- lets_plot/plot/plot.py +245 -0
- lets_plot/plot/pos.py +344 -0
- lets_plot/plot/sampling.py +338 -0
- lets_plot/plot/sandbox_.py +26 -0
- lets_plot/plot/scale.py +3580 -0
- lets_plot/plot/scale_colormap_mpl.py +300 -0
- lets_plot/plot/scale_convenience.py +155 -0
- lets_plot/plot/scale_identity_.py +653 -0
- lets_plot/plot/scale_position.py +1342 -0
- lets_plot/plot/series_meta.py +209 -0
- lets_plot/plot/stat.py +585 -0
- lets_plot/plot/subplots.py +331 -0
- lets_plot/plot/subplots_util.py +24 -0
- lets_plot/plot/theme_.py +790 -0
- lets_plot/plot/theme_set.py +418 -0
- lets_plot/plot/tooltip.py +486 -0
- lets_plot/plot/util.py +267 -0
- lets_plot/settings_utils.py +244 -0
- lets_plot/tilesets.py +429 -0
- lets_plot-4.8.1rc1.dist-info/METADATA +221 -0
- lets_plot-4.8.1rc1.dist-info/RECORD +97 -0
- lets_plot-4.8.1rc1.dist-info/WHEEL +6 -0
- lets_plot-4.8.1rc1.dist-info/licenses/LICENSE +21 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.FreeType +166 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.ImageMagick +106 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.expat +21 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.fontconfig +200 -0
- lets_plot-4.8.1rc1.dist-info/top_level.txt +2 -0
- lets_plot_kotlin_bridge.cpython-311-x86_64-linux-gnu.so +0 -0
lets_plot/plot/util.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2019. JetBrains s.r.o.
|
|
3
|
+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
|
|
4
|
+
#
|
|
5
|
+
from typing import Any, Tuple, Sequence, Optional, Dict, List
|
|
6
|
+
|
|
7
|
+
from lets_plot._type_utils import is_pandas_data_frame, is_polars_dataframe
|
|
8
|
+
from lets_plot.geo_data_internals.utils import find_geo_names
|
|
9
|
+
from lets_plot.mapping import MappingMeta
|
|
10
|
+
from lets_plot.plot.core import aes, FeatureSpec, PlotSpec
|
|
11
|
+
from lets_plot.plot.series_meta import _infer_type, TYPE_UNKNOWN, TYPE_DATE_TIME, _detect_time_zone
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def as_boolean(val, *, default):
|
|
15
|
+
if val is None:
|
|
16
|
+
return default
|
|
17
|
+
|
|
18
|
+
return bool(val) and val != 'False'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def update_plot_aes_mapping(plot: PlotSpec, add_mapping: FeatureSpec):
|
|
22
|
+
existing_spec = plot.props().get('mapping', aes())
|
|
23
|
+
merged_mapping = {**existing_spec.as_dict(), **add_mapping.as_dict()}
|
|
24
|
+
|
|
25
|
+
# Re-annotate the data with the merged mapping.
|
|
26
|
+
data = plot.props().get('data', None)
|
|
27
|
+
data, processed_mapping, data_meta = as_annotated_data(data, aes(**merged_mapping))
|
|
28
|
+
plot.props()['data'] = data
|
|
29
|
+
plot.props()['mapping'] = processed_mapping
|
|
30
|
+
|
|
31
|
+
# Add data_meta to plot properties
|
|
32
|
+
for key, value in data_meta.items():
|
|
33
|
+
plot.props()[key] = value
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def as_annotated_data(data: Any, mapping_spec: FeatureSpec) -> Tuple:
|
|
37
|
+
data_type_by_var: Dict[str, str] = {} # VarName to Type
|
|
38
|
+
mapping_meta_by_var: Dict[str, Dict[str, MappingMeta]] = {} # VarName to Dict[Aes, MappingMeta]
|
|
39
|
+
mappings = {} # Aes to VarName
|
|
40
|
+
|
|
41
|
+
# fill mapping_meta_by_var, mappings and data_type_by_var.
|
|
42
|
+
if mapping_spec is not None:
|
|
43
|
+
for key, spec in mapping_spec.props().items():
|
|
44
|
+
# the key is either an aesthetic name or 'name' (FeatureSpec.name property)
|
|
45
|
+
if key == 'name': # ignore FeatureSpec.name property
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
if isinstance(spec, MappingMeta):
|
|
49
|
+
mappings[key] = spec.variable
|
|
50
|
+
mapping_meta_by_var.setdefault(spec.variable, {})[key] = spec
|
|
51
|
+
data_type_by_var[spec.variable] = TYPE_UNKNOWN
|
|
52
|
+
else:
|
|
53
|
+
mappings[key] = spec # spec is a variable name
|
|
54
|
+
|
|
55
|
+
data_type_by_var.update(_infer_type(data))
|
|
56
|
+
|
|
57
|
+
# Detect the tome zone - one for the entire data set.
|
|
58
|
+
time_zone_by_var_name = {}
|
|
59
|
+
for var_name, data_type in data_type_by_var.items():
|
|
60
|
+
if data_type == TYPE_DATE_TIME:
|
|
61
|
+
time_zone = _detect_time_zone(var_name, data)
|
|
62
|
+
if time_zone is not None:
|
|
63
|
+
time_zone_by_var_name[var_name] = time_zone
|
|
64
|
+
|
|
65
|
+
# fill series annotations
|
|
66
|
+
series_annotations = {} # var to series_annotation
|
|
67
|
+
for var_name, data_type in data_type_by_var.items():
|
|
68
|
+
series_annotation = {}
|
|
69
|
+
|
|
70
|
+
if data_type != TYPE_UNKNOWN:
|
|
71
|
+
series_annotation['type'] = data_type
|
|
72
|
+
|
|
73
|
+
if var_name in time_zone_by_var_name:
|
|
74
|
+
series_annotation['time_zone'] = time_zone_by_var_name[var_name]
|
|
75
|
+
|
|
76
|
+
if is_pandas_data_frame(data) and data[var_name].dtype.name == 'category' and data[var_name].dtype.ordered:
|
|
77
|
+
series_annotation['factor_levels'] = data[var_name].cat.categories.to_list()
|
|
78
|
+
|
|
79
|
+
elif is_polars_dataframe(data):
|
|
80
|
+
import polars
|
|
81
|
+
|
|
82
|
+
col_dtype = data[var_name].dtype
|
|
83
|
+
if isinstance(col_dtype, polars.datatypes.Enum):
|
|
84
|
+
series_annotation['factor_levels'] = list(col_dtype.categories)
|
|
85
|
+
elif isinstance(col_dtype, polars.datatypes.Categorical):
|
|
86
|
+
# # It does not seem possible to get categories in correct order from the Categorical dtype.
|
|
87
|
+
# categories_series = data[var_name].cat.get_categories()
|
|
88
|
+
# indises = [col_dtype.categories[cat] for cat in categories_series]
|
|
89
|
+
# cats = [col_dtype.categories[i] for i in indises]
|
|
90
|
+
# series_annotation['factor_levels'] = categories_series.to_list()
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
elif var_name in mapping_meta_by_var:
|
|
94
|
+
levels = last_not_none(list(map(lambda mm: mm.levels, mapping_meta_by_var[var_name].values())))
|
|
95
|
+
if levels is not None:
|
|
96
|
+
series_annotation['factor_levels'] = levels
|
|
97
|
+
|
|
98
|
+
if 'factor_levels' in series_annotation and var_name in mapping_meta_by_var:
|
|
99
|
+
order = last_not_none(list(map(lambda mm: mm.parameters['order'], mapping_meta_by_var[var_name].values())))
|
|
100
|
+
if order is not None:
|
|
101
|
+
series_annotation['order'] = order
|
|
102
|
+
|
|
103
|
+
if len(series_annotation) > 0:
|
|
104
|
+
series_annotation['column'] = var_name
|
|
105
|
+
series_annotations[var_name] = series_annotation
|
|
106
|
+
|
|
107
|
+
# fill mapping annotations
|
|
108
|
+
mapping_annotations = []
|
|
109
|
+
for var_name, meta_data in mapping_meta_by_var.items():
|
|
110
|
+
for aesthetic, mapping_meta in meta_data.items():
|
|
111
|
+
if mapping_meta.annotation == 'as_discrete':
|
|
112
|
+
if 'factor_levels' in series_annotations.get(var_name, {}):
|
|
113
|
+
# there is a bug - if label is set then levels are not applied
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
mapping_annotation = {}
|
|
117
|
+
|
|
118
|
+
# Note that the label is always set; otherwise, the scale title will appear as 'color.cyl'
|
|
119
|
+
label = mapping_meta.parameters.get('label')
|
|
120
|
+
if label is not None:
|
|
121
|
+
mapping_annotation.setdefault('parameters', {})['label'] = label
|
|
122
|
+
|
|
123
|
+
if mapping_meta.levels is not None:
|
|
124
|
+
mapping_annotation['levels'] = mapping_meta.levels
|
|
125
|
+
|
|
126
|
+
order_by = mapping_meta.parameters.get('order_by')
|
|
127
|
+
if order_by is not None:
|
|
128
|
+
mapping_annotation.setdefault('parameters', {})['order_by'] = order_by
|
|
129
|
+
|
|
130
|
+
order = mapping_meta.parameters.get('order')
|
|
131
|
+
if order is not None:
|
|
132
|
+
mapping_annotation.setdefault('parameters', {})['order'] = order
|
|
133
|
+
|
|
134
|
+
# add mapping meta if a custom label is set or if series annotation for var doesn't contain order options
|
|
135
|
+
# otherwise don't add mapping meta - it's redundant, nothing unique compared to series annotation
|
|
136
|
+
if len(mapping_annotation):
|
|
137
|
+
mapping_annotation['aes'] = aesthetic
|
|
138
|
+
mapping_annotation['annotation'] = 'as_discrete'
|
|
139
|
+
mapping_annotations.append(mapping_annotation)
|
|
140
|
+
|
|
141
|
+
data_meta = {}
|
|
142
|
+
|
|
143
|
+
if len(series_annotations) > 0:
|
|
144
|
+
data_meta.update({'series_annotations': list(series_annotations.values())})
|
|
145
|
+
|
|
146
|
+
if len(mapping_annotations) > 0:
|
|
147
|
+
data_meta.update({'mapping_annotations': mapping_annotations})
|
|
148
|
+
|
|
149
|
+
return data, aes(**mappings), {'data_meta': data_meta}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def is_data_pub_stream(data: Any) -> bool:
|
|
153
|
+
# try:
|
|
154
|
+
# from lets_plot.display import DataPubStream
|
|
155
|
+
# return isinstance(data, DataPubStream)
|
|
156
|
+
# except ImportError:
|
|
157
|
+
# return False # no pub-sub in standalone deployment
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def normalize_map_join(map_join):
|
|
162
|
+
if map_join is None:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
def invalid_map_join_format():
|
|
166
|
+
return ValueError("map_join must be a str, list[str] or pair of list[str]")
|
|
167
|
+
|
|
168
|
+
if isinstance(map_join, str): # 'foo' -> [['foo'], None]
|
|
169
|
+
data_names = [map_join]
|
|
170
|
+
map_names = None
|
|
171
|
+
elif isinstance(map_join, Sequence):
|
|
172
|
+
if all(isinstance(v, str) for v in map_join): # all items are strings
|
|
173
|
+
if len(map_join) == 1: # ['foo'] -> [['foo'], None]
|
|
174
|
+
data_names = map_join
|
|
175
|
+
map_names = None
|
|
176
|
+
elif len(map_join) == 2: # ['foo', 'bar'] -> [['foo'], ['bar']]
|
|
177
|
+
data_names = [map_join[0]]
|
|
178
|
+
map_names = [map_join[1]]
|
|
179
|
+
elif len(map_join) > 2: # ['foo', 'bar', 'baz'] -> error
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"map_join of type list[str] expected to have 1 or 2 items, but was {}".format(len(map_join)))
|
|
182
|
+
else:
|
|
183
|
+
raise invalid_map_join_format()
|
|
184
|
+
elif all(isinstance(v, Sequence) and not isinstance(v, str) for v in map_join): # all items are lists
|
|
185
|
+
if len(map_join) == 1: # [['foo', 'bar']] -> [['foo', 'bar'], None]
|
|
186
|
+
data_names = map_join[0]
|
|
187
|
+
map_names = None
|
|
188
|
+
elif len(map_join) == 2: # [['foo', 'bar'], ['baz', 'qux']] -> [['foo', 'bar'], ['baz', 'qux']]
|
|
189
|
+
data_names = map_join[0]
|
|
190
|
+
map_names = map_join[1]
|
|
191
|
+
else:
|
|
192
|
+
raise invalid_map_join_format()
|
|
193
|
+
else:
|
|
194
|
+
raise invalid_map_join_format()
|
|
195
|
+
|
|
196
|
+
else:
|
|
197
|
+
raise invalid_map_join_format()
|
|
198
|
+
|
|
199
|
+
return [data_names, map_names]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def auto_join_geo_names(map_join: Any, gdf):
|
|
203
|
+
if map_join is None:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
data_names = map_join[0]
|
|
207
|
+
map_names = map_join[1]
|
|
208
|
+
|
|
209
|
+
if map_names is None:
|
|
210
|
+
map_names = find_geo_names(gdf)
|
|
211
|
+
if len(map_names) == 0:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
"Can't deduce joining keys.\n"
|
|
214
|
+
"Define both data and map key columns in map_join "
|
|
215
|
+
"explicitly: map_join=[['data_column'], ['map_column']]."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if len(data_names) > len(map_names):
|
|
219
|
+
raise ValueError(
|
|
220
|
+
"Data key columns count exceeds map key columns count: {} > {}".format(len(data_names), len(map_names))
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
map_names = map_names[:len(data_names)] # use same number of key columns
|
|
224
|
+
|
|
225
|
+
return [data_names, map_names]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def is_geo_data_frame(data: Any) -> bool:
|
|
229
|
+
try:
|
|
230
|
+
from geopandas import GeoDataFrame
|
|
231
|
+
return isinstance(data, GeoDataFrame)
|
|
232
|
+
except ImportError:
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def get_geo_data_frame_meta(geo_data_frame) -> dict:
|
|
237
|
+
return {
|
|
238
|
+
'geodataframe': {
|
|
239
|
+
'geometry': geo_data_frame.geometry.name
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def geo_data_frame_to_crs(gdf: 'GeoDataFrame', use_crs: Optional[str]):
|
|
245
|
+
if gdf.crs is None:
|
|
246
|
+
return gdf
|
|
247
|
+
|
|
248
|
+
return gdf.to_crs('EPSG:4326' if use_crs is None else use_crs)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def key_int2str(data):
|
|
252
|
+
if is_pandas_data_frame(data):
|
|
253
|
+
if data.columns.inferred_type == 'integer' or data.columns.inferred_type == 'mixed-integer':
|
|
254
|
+
data.columns = data.columns.astype(str)
|
|
255
|
+
return data
|
|
256
|
+
|
|
257
|
+
if isinstance(data, dict):
|
|
258
|
+
return {(str(k) if isinstance(k, int) else k): v for k, v in data.items()}
|
|
259
|
+
|
|
260
|
+
return data
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def last_not_none(lst: List) -> Optional[Any]:
|
|
264
|
+
for i in reversed(lst):
|
|
265
|
+
if i is not None:
|
|
266
|
+
return i
|
|
267
|
+
return None
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Copyright (c) 2020. JetBrains s.r.o.
|
|
2
|
+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
|
|
3
|
+
|
|
4
|
+
from ._global_settings import GEOCODING_PROVIDER_URL, MAPTILES_SOLID_FILL_COLOR, TILES_CHESSBOARD, \
|
|
5
|
+
_DATALORE_TILES_SERVICE
|
|
6
|
+
from ._global_settings import MAPTILES_KIND, MAPTILES_URL, MAPTILES_THEME, MAPTILES_ATTRIBUTION, MAPTILES_MIN_ZOOM, \
|
|
7
|
+
MAPTILES_MAX_ZOOM, TILES_VECTOR_LETS_PLOT, TILES_RASTER_ZXY, TILES_SOLID, _DATALORE_TILES_ATTRIBUTION
|
|
8
|
+
from ._global_settings import has_global_value, get_global_val, _DATALORE_TILES_MIN_ZOOM, _DATALORE_TILES_MAX_ZOOM
|
|
9
|
+
|
|
10
|
+
__all__ = ['maptiles_zxy', 'maptiles_lets_plot', 'maptiles_solid']
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def maptiles_lets_plot(url: str = None, theme: str = None) -> dict:
|
|
14
|
+
"""
|
|
15
|
+
Make vector tiles config. Can be used individually in `geom_livemap() <https://lets-plot.org/python/pages/api/lets_plot.geom_livemap.html>`__
|
|
16
|
+
or in every livemap via `LetsPlot.set() <https://lets-plot.org/python/pages/api/lets_plot.LetsPlot.html#lets_plot.LetsPlot.set>`__.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
url : str
|
|
21
|
+
Address of the tile server. Can be omitted if URL is already set in global settings.
|
|
22
|
+
|
|
23
|
+
theme : {'color', 'light', 'dark', 'bw'}
|
|
24
|
+
Tiles theme.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
dict
|
|
29
|
+
Tile provider settings.
|
|
30
|
+
|
|
31
|
+
Notes
|
|
32
|
+
-----
|
|
33
|
+
If you are using Safari and having trouble loading tiles, try disabling the NSURLSession Websocket feature.
|
|
34
|
+
Go to `Develop -> Experimental Features -> NSURLSession Websocket` to turn it off.
|
|
35
|
+
|
|
36
|
+
Also, you could use raster tiles from ``lets_plot.tilesets``, e.g.
|
|
37
|
+
``ggplot() + geom_livemap(tiles=tilesets.OPEN_TOPO_MAP)``.
|
|
38
|
+
|
|
39
|
+
Examples
|
|
40
|
+
--------
|
|
41
|
+
.. jupyter-execute::
|
|
42
|
+
:linenos:
|
|
43
|
+
:emphasize-lines: 3
|
|
44
|
+
|
|
45
|
+
from lets_plot import *
|
|
46
|
+
LetsPlot.setup_html()
|
|
47
|
+
tiles = maptiles_lets_plot(url='wss://tiles.datalore.jetbrains.com', theme='light')
|
|
48
|
+
ggplot() + geom_livemap(tiles=tiles)
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
assert isinstance(url, (str, type(None))), "'url' argument is not str: {}".format(type(url))
|
|
52
|
+
assert isinstance(theme, (str, type(None))), "'theme' argument is not str: {}".format(type(theme))
|
|
53
|
+
|
|
54
|
+
if url is None:
|
|
55
|
+
global_maptiles_kind = get_global_val(MAPTILES_KIND) if has_global_value(MAPTILES_KIND) else None
|
|
56
|
+
global_maptiles_url = get_global_val(MAPTILES_URL) if has_global_value(MAPTILES_URL) else None
|
|
57
|
+
|
|
58
|
+
# try to read url from global settings
|
|
59
|
+
if global_maptiles_kind == TILES_VECTOR_LETS_PLOT:
|
|
60
|
+
if global_maptiles_url is None:
|
|
61
|
+
# global URL is somehow broken - use default URL
|
|
62
|
+
url = _DATALORE_TILES_SERVICE
|
|
63
|
+
else:
|
|
64
|
+
url = global_maptiles_url
|
|
65
|
+
else:
|
|
66
|
+
# User input:
|
|
67
|
+
# LetsPlot.set(maptiles_zxy(...))
|
|
68
|
+
# LetsPlot.set(maptiles_lets_plot(...))
|
|
69
|
+
# In this case global_maptiles_url will contain not-applicable raster tile URL.
|
|
70
|
+
# Use hardcoded lets_plot tiles URL.
|
|
71
|
+
url = _DATALORE_TILES_SERVICE
|
|
72
|
+
|
|
73
|
+
if url is None:
|
|
74
|
+
raise ValueError('lets_plot tiles service URL is not defined')
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
MAPTILES_KIND: TILES_VECTOR_LETS_PLOT,
|
|
78
|
+
MAPTILES_URL: url,
|
|
79
|
+
MAPTILES_THEME: theme,
|
|
80
|
+
MAPTILES_ATTRIBUTION: _DATALORE_TILES_ATTRIBUTION,
|
|
81
|
+
MAPTILES_MIN_ZOOM: _DATALORE_TILES_MIN_ZOOM,
|
|
82
|
+
MAPTILES_MAX_ZOOM: _DATALORE_TILES_MAX_ZOOM,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def maptiles_zxy(url: str, attribution: str = None, min_zoom: int = None, max_zoom: int = None, subdomains: str = None,
|
|
87
|
+
**other_args) -> dict:
|
|
88
|
+
"""
|
|
89
|
+
Make raster tiles config. Can be used individually in `geom_livemap() <https://lets-plot.org/python/pages/api/lets_plot.geom_livemap.html>`__
|
|
90
|
+
or in every livemap via `LetsPlot.set() <https://lets-plot.org/python/pages/api/lets_plot.LetsPlot.html#lets_plot.LetsPlot.set>`__.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
url : str
|
|
95
|
+
Template for a standard raster ZXY tile provider with {z}, {x}, {y} and {s} placeholders,
|
|
96
|
+
e.g. ``"https://{s}.tile.com/{z}/{x}/{y}.png"``. Where {z} means zoom, {x} and {y} means
|
|
97
|
+
tile coordinate, {s} means subdomains.
|
|
98
|
+
attribution : str
|
|
99
|
+
An attribution or a copyright notice to display on the map as required by the tile license.
|
|
100
|
+
Supports HTML links: ``'<a href="http://www.example.com">Example</a>'``.
|
|
101
|
+
min_zoom : int
|
|
102
|
+
Minimal zoom limit, an integer from 1 to 15. Should be less than or equal to ``max_zoom``.
|
|
103
|
+
max_zoom : int
|
|
104
|
+
Maximal zoom limit, an integer from 1 to 15. Should be greater than or equal to ``min_zoom``.
|
|
105
|
+
subdomains : str
|
|
106
|
+
Each character of this list is interpreted as standalone tile servers, so an interactive map
|
|
107
|
+
can request tiles from any of these servers independently for better load balance. If url
|
|
108
|
+
contains {s} placeholder and subdomains parameter is not set default string 'abc' will be used.
|
|
109
|
+
other_args
|
|
110
|
+
Any key-value pairs that can be substituted into the URL template, e.g.
|
|
111
|
+
``maptiles_zxy(url='http://maps.example.com/{z}/{x}/{y}.png?access_key={key}', key='MY_ACCESS_KEY')``.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
dict
|
|
116
|
+
Tile provider settings.
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
.. jupyter-execute::
|
|
121
|
+
:linenos:
|
|
122
|
+
:emphasize-lines: 3-7
|
|
123
|
+
|
|
124
|
+
from lets_plot import *
|
|
125
|
+
LetsPlot.setup_html()
|
|
126
|
+
tiles = maptiles_zxy(
|
|
127
|
+
url="https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/VIIRS_CityLights_2012/default/GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpg",
|
|
128
|
+
attribution='<a href="https://earthdata.nasa.gov/eosdis/science-system-description/eosdis-components/gibs">© NASA Global Imagery Browse Services (GIBS)</a>',
|
|
129
|
+
max_zoom=8
|
|
130
|
+
)
|
|
131
|
+
ggplot() + geom_livemap(tiles=tiles)
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
assert isinstance(url, str), "'url' argument is not str: {}".format(type(url))
|
|
135
|
+
assert isinstance(attribution, (str, type(None))), "'attribution' argument is not str: {}".format(type(url))
|
|
136
|
+
if subdomains is not None and "{s}" not in url:
|
|
137
|
+
raise ValueError("Subdomains are set but {s} placeholder is not found in url: " + subdomains)
|
|
138
|
+
|
|
139
|
+
for k, v in other_args.items():
|
|
140
|
+
assert k not in ["x", "y", "z", "s"], "other_args can't contain keys x, y, z and s"
|
|
141
|
+
url = url.replace("{" + k + "}", v)
|
|
142
|
+
|
|
143
|
+
if subdomains is not None and "{s}" in url:
|
|
144
|
+
url = url.replace("{s}", '[' + subdomains + ']')
|
|
145
|
+
elif subdomains is None and "{s}" in url:
|
|
146
|
+
url = url.replace("{s}", '[abc]')
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
MAPTILES_KIND: TILES_RASTER_ZXY,
|
|
150
|
+
MAPTILES_URL: url,
|
|
151
|
+
MAPTILES_ATTRIBUTION: _build_attribution(attribution),
|
|
152
|
+
MAPTILES_MIN_ZOOM: min_zoom,
|
|
153
|
+
MAPTILES_MAX_ZOOM: max_zoom
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def maptiles_solid(color: str):
|
|
158
|
+
"""
|
|
159
|
+
Make solid color tiles config. Can be used individually in `geom_livemap() <https://lets-plot.org/python/pages/api/lets_plot.geom_livemap.html>`__
|
|
160
|
+
or in every livemap via `LetsPlot.set() <https://lets-plot.org/python/pages/api/lets_plot.LetsPlot.html#lets_plot.LetsPlot.set>`__.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
color : str
|
|
165
|
+
Color in HEX format.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
dict
|
|
170
|
+
Tile provider settings.
|
|
171
|
+
|
|
172
|
+
Examples
|
|
173
|
+
--------
|
|
174
|
+
.. jupyter-execute::
|
|
175
|
+
:linenos:
|
|
176
|
+
:emphasize-lines: 5
|
|
177
|
+
|
|
178
|
+
from lets_plot import *
|
|
179
|
+
from lets_plot.geo_data import *
|
|
180
|
+
LetsPlot.setup_html()
|
|
181
|
+
nyc = geocode_cities('New York').get_boundaries()
|
|
182
|
+
tiles = maptiles_solid(color='#d3d3d3')
|
|
183
|
+
ggplot() + geom_livemap(tiles=tiles) + geom_map(data=nyc)
|
|
184
|
+
|
|
185
|
+
"""
|
|
186
|
+
return {
|
|
187
|
+
MAPTILES_KIND: TILES_SOLID,
|
|
188
|
+
MAPTILES_SOLID_FILL_COLOR: color
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def maptiles_chessboard():
|
|
193
|
+
"""
|
|
194
|
+
Make solid color tiles with chessboard pattern. Can be used individually in `geom_livemap() <https://lets-plot.org/python/pages/api/lets_plot.geom_livemap.html>`__
|
|
195
|
+
or in every livemap via `LetsPlot.set() <https://lets-plot.org/python/pages/api/lets_plot.LetsPlot.html#lets_plot.LetsPlot.set>`__.
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
dict
|
|
200
|
+
Tile provider settings.
|
|
201
|
+
|
|
202
|
+
Examples
|
|
203
|
+
--------
|
|
204
|
+
.. jupyter-execute::
|
|
205
|
+
:linenos:
|
|
206
|
+
:emphasize-lines: 3
|
|
207
|
+
|
|
208
|
+
from lets_plot.settings_utils import maptiles_chessboard
|
|
209
|
+
LetsPlot.setup_html()
|
|
210
|
+
ggplot() + geom_livemap(tiles=maptiles_chessboard())
|
|
211
|
+
|
|
212
|
+
"""
|
|
213
|
+
return {
|
|
214
|
+
MAPTILES_KIND: TILES_CHESSBOARD
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def geocoding_service(url: str):
|
|
219
|
+
"""
|
|
220
|
+
Make geocoding service config.
|
|
221
|
+
Can be applied via LetsPlot.set(...)
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
url : string
|
|
226
|
+
Address of the geocoding server
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
Geocoding service settings
|
|
231
|
+
"""
|
|
232
|
+
assert isinstance(url, str), "'url' argument is not str: {}".format(type(url))
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
GEOCODING_PROVIDER_URL: url
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _build_attribution(other_attributions):
|
|
240
|
+
map_attribution = '<a href="https://lets-plot.org">\u00a9 Lets-Plot</a>'
|
|
241
|
+
if other_attributions is None:
|
|
242
|
+
return map_attribution
|
|
243
|
+
else:
|
|
244
|
+
return map_attribution + ', ' + other_attributions
|