geovizpy 0.1.7__py3-none-any.whl → 0.1.8__py3-none-any.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.
- docs/source/conf.py +32 -0
- examples/advanced_plots.py +82 -0
- examples/bubble.py +46 -0
- examples/choropleth.py +45 -0
- examples/choropleth_effect.py +62 -0
- examples/json_export.py +37 -0
- examples/labels.py +43 -0
- examples/layer_control.py +70 -0
- examples/lines.py +74 -0
- examples/reactive.py +37 -0
- examples/simple.py +38 -0
- examples/tiles.py +47 -0
- geovizpy/controls.py +20 -0
- geovizpy/geoviz.py +2 -3
- geovizpy/insets.py +0 -20
- geovizpy/renderer.py +131 -17
- {geovizpy-0.1.7.dist-info → geovizpy-0.1.8.dist-info}/METADATA +112 -118
- geovizpy-0.1.8.dist-info/RECORD +25 -0
- {geovizpy-0.1.7.dist-info → geovizpy-0.1.8.dist-info}/WHEEL +1 -1
- geovizpy-0.1.8.dist-info/top_level.txt +4 -0
- geovizpy-0.1.7.dist-info/RECORD +0 -13
- geovizpy-0.1.7.dist-info/top_level.txt +0 -1
docs/source/conf.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Configuration file for the Sphinx documentation builder.
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
sys.path.insert(0, os.path.abspath('../../'))
|
|
5
|
+
|
|
6
|
+
project = 'geovizpy'
|
|
7
|
+
copyright = '2026, fbxyz'
|
|
8
|
+
author = 'fbxyz'
|
|
9
|
+
release = '0.1.3'
|
|
10
|
+
|
|
11
|
+
# -- General configuration ---------------------------------------------------
|
|
12
|
+
extensions = [
|
|
13
|
+
'sphinx.ext.autodoc',
|
|
14
|
+
'sphinx.ext.napoleon',
|
|
15
|
+
'sphinx.ext.viewcode',
|
|
16
|
+
'sphinx.ext.githubpages',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
templates_path = ['_templates']
|
|
20
|
+
exclude_patterns = []
|
|
21
|
+
|
|
22
|
+
# -- Options for HTML output -------------------------------------------------
|
|
23
|
+
try:
|
|
24
|
+
import sphinx_rtd_theme
|
|
25
|
+
html_theme = 'sphinx_rtd_theme'
|
|
26
|
+
except ImportError:
|
|
27
|
+
html_theme = 'alabaster'
|
|
28
|
+
|
|
29
|
+
html_static_path = ['_static']
|
|
30
|
+
|
|
31
|
+
# -- Options for autodoc -----------------------------------------------------
|
|
32
|
+
autodoc_member_order = 'bysource'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Example script demonstrating advanced plot types."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
from geovizpy import Geoviz
|
|
7
|
+
|
|
8
|
+
# Define output directory
|
|
9
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
10
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
11
|
+
|
|
12
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
13
|
+
try:
|
|
14
|
+
with open(data_path) as f:
|
|
15
|
+
world_data = json.load(f)
|
|
16
|
+
except FileNotFoundError:
|
|
17
|
+
print(f"{data_path} not found.")
|
|
18
|
+
world_data = {}
|
|
19
|
+
|
|
20
|
+
continents = ["Africa", "Asia", "Europe", "North America", "South America", "Oceania"]
|
|
21
|
+
symbols_list = ["star", "circle", "cross", "diamond", "square", "triangle"]
|
|
22
|
+
|
|
23
|
+
for f in world_data.get("features", []):
|
|
24
|
+
f["properties"]["random_continent"] = random.choice(continents)
|
|
25
|
+
f["properties"]["random_symbol"] = random.choice(symbols_list)
|
|
26
|
+
|
|
27
|
+
# 1. Typology Map
|
|
28
|
+
viz1 = Geoviz(projection="EqualEarth", width=800)
|
|
29
|
+
viz1.outline()
|
|
30
|
+
viz1.graticule(stroke="white", strokeWidth=0.4)
|
|
31
|
+
|
|
32
|
+
viz1.typo(
|
|
33
|
+
data=world_data,
|
|
34
|
+
var="random_continent",
|
|
35
|
+
colors="Pastel",
|
|
36
|
+
strokeWidth=0.5,
|
|
37
|
+
leg_title="Random Continent",
|
|
38
|
+
leg_pos=[10, 200]
|
|
39
|
+
)
|
|
40
|
+
viz1.header(text="Typology Map (Random Data)", fontSize=20)
|
|
41
|
+
viz1.render_html(os.path.join(output_dir, "advanced_typo.html"))
|
|
42
|
+
|
|
43
|
+
# 2. Proportional Symbols with Typology
|
|
44
|
+
viz2 = Geoviz(projection="EqualEarth", width=800)
|
|
45
|
+
viz2.outline()
|
|
46
|
+
viz2.graticule()
|
|
47
|
+
viz2.path(datum=world_data, fill="#eee")
|
|
48
|
+
|
|
49
|
+
viz2.proptypo(
|
|
50
|
+
data=world_data,
|
|
51
|
+
var1="pop",
|
|
52
|
+
var2="random_continent",
|
|
53
|
+
k=50,
|
|
54
|
+
colors="Set1",
|
|
55
|
+
leg1_title="Population",
|
|
56
|
+
leg2_title="Continent",
|
|
57
|
+
leg1_pos=[10, 300],
|
|
58
|
+
leg2_pos=[10, 150]
|
|
59
|
+
)
|
|
60
|
+
viz2.header(text="Proportional Symbols + Typology", fontSize=20)
|
|
61
|
+
viz2.render_html(os.path.join(output_dir, "advanced_proptypo.html"))
|
|
62
|
+
|
|
63
|
+
# 3. Symbol Map
|
|
64
|
+
viz3 = Geoviz(projection="EqualEarth", width=800)
|
|
65
|
+
viz3.outline()
|
|
66
|
+
viz3.graticule()
|
|
67
|
+
viz3.path(datum=world_data, fill="#ddd")
|
|
68
|
+
|
|
69
|
+
viz3.picto(
|
|
70
|
+
data=world_data,
|
|
71
|
+
var="random_symbol",
|
|
72
|
+
symbols=["star", "circle", "cross", "diamond", "square", "triangle"],
|
|
73
|
+
colors="black",
|
|
74
|
+
fill="red",
|
|
75
|
+
stroke="white",
|
|
76
|
+
strokeWidth=1,
|
|
77
|
+
k=15,
|
|
78
|
+
leg_title="Random Symbols",
|
|
79
|
+
leg_pos=[10, 200]
|
|
80
|
+
)
|
|
81
|
+
viz3.header(text="Symbol Map (Picto)", fontSize=20)
|
|
82
|
+
viz3.render_html(os.path.join(output_dir, "advanced_symbol.html"))
|
examples/bubble.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Example script for creating a proportional symbol map (bubble map)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
12
|
+
try:
|
|
13
|
+
with open(data_path) as f:
|
|
14
|
+
world_data = json.load(f)
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
print(f"{data_path} not found.")
|
|
17
|
+
world_data = {}
|
|
18
|
+
|
|
19
|
+
viz = Geoviz(projection="EqualEarth", zoomable=True)
|
|
20
|
+
viz.outline()
|
|
21
|
+
viz.graticule(stroke="white", strokeWidth=0.4)
|
|
22
|
+
viz.path(datum=world_data, fill="white", fillOpacity=0.4)
|
|
23
|
+
|
|
24
|
+
# Use viz.prop instead of viz.circle to get automatic legend handling
|
|
25
|
+
viz.prop(
|
|
26
|
+
data=world_data,
|
|
27
|
+
var="pop", # 'var' is used instead of 'r' in viz.prop
|
|
28
|
+
fill="#f07d75",
|
|
29
|
+
tip="(d) => `${d.properties.NAMEen}\n${d.properties.pop / 1000} thousands inh.`",
|
|
30
|
+
leg_type="separate",
|
|
31
|
+
leg_title="Population",
|
|
32
|
+
leg_pos=[30, 30], # Added position to make sure it's visible
|
|
33
|
+
leg_title_fontSize=15
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
pops = [f["properties"]["pop"] for f in world_data.get("features", []) if "pop" in f["properties"]]
|
|
37
|
+
|
|
38
|
+
viz.header(
|
|
39
|
+
fontSize=30,
|
|
40
|
+
text="A World Map With Bubbles",
|
|
41
|
+
fill="#267A8A",
|
|
42
|
+
fontWeight="bold",
|
|
43
|
+
fontFamily="Tangerine"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
viz.render_html(os.path.join(output_dir, "bubble.html"))
|
examples/choropleth.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Example script for a choropleth map."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
12
|
+
try:
|
|
13
|
+
with open(data_path) as f:
|
|
14
|
+
world_data = json.load(f)
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
print(f"{data_path} not found.")
|
|
17
|
+
world_data = {}
|
|
18
|
+
|
|
19
|
+
viz = Geoviz(projection="EqualEarth")
|
|
20
|
+
viz.outline()
|
|
21
|
+
viz.graticule(stroke="white", step=30, strokeWidth=1.2)
|
|
22
|
+
|
|
23
|
+
viz.choro(
|
|
24
|
+
data=world_data,
|
|
25
|
+
var="gdppc",
|
|
26
|
+
strokeWidth=0.3,
|
|
27
|
+
tip=True,
|
|
28
|
+
leg_type="horizontal",
|
|
29
|
+
leg_title="GDP per capita",
|
|
30
|
+
leg_subtitle="($ per inh.)",
|
|
31
|
+
leg_note="Source: worldbank, 2020",
|
|
32
|
+
leg_pos=[410, 370],
|
|
33
|
+
leg_values_round=0,
|
|
34
|
+
leg_missing_text="Missing values"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
viz.header(
|
|
38
|
+
fontSize=30,
|
|
39
|
+
text="A Choropleth World Map",
|
|
40
|
+
fill="#267A8A",
|
|
41
|
+
fontWeight="bold",
|
|
42
|
+
fontFamily="Tangerine"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
viz.render_html(os.path.join(output_dir, "choropleth.html"))
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Example script demonstrating visual effects (shadow)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
12
|
+
try:
|
|
13
|
+
with open(data_path) as f:
|
|
14
|
+
world_data = json.load(f)
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
print(f"{data_path} not found.")
|
|
17
|
+
world_data = {}
|
|
18
|
+
|
|
19
|
+
brazil = {
|
|
20
|
+
"type": "FeatureCollection",
|
|
21
|
+
"features": [f for f in world_data.get("features", []) if f["properties"].get("NAMEen") == "Brazil"]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
viz = Geoviz(projection="EqualEarth")
|
|
25
|
+
|
|
26
|
+
viz.effect_shadow(id="myshadow", dx=10, dy=10, opacity=0.5)
|
|
27
|
+
|
|
28
|
+
viz.outline()
|
|
29
|
+
viz.graticule(stroke="white", step=30, strokeWidth=1.2)
|
|
30
|
+
|
|
31
|
+
viz.choro(
|
|
32
|
+
data=world_data,
|
|
33
|
+
var="gdppc",
|
|
34
|
+
strokeWidth=0.3,
|
|
35
|
+
tip=True,
|
|
36
|
+
leg_type="horizontal",
|
|
37
|
+
leg_title="GDP per capita",
|
|
38
|
+
leg_subtitle="($ per inh.)",
|
|
39
|
+
leg_note="Source: worldbank, 2020",
|
|
40
|
+
leg_pos=[410, 370],
|
|
41
|
+
leg_values_round=0,
|
|
42
|
+
leg_missing_text="Missing values"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if brazil["features"]:
|
|
46
|
+
viz.path(
|
|
47
|
+
data=brazil,
|
|
48
|
+
fill="none",
|
|
49
|
+
stroke="red",
|
|
50
|
+
strokeWidth=2,
|
|
51
|
+
filter="url(#myshadow)"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
viz.header(
|
|
55
|
+
fontSize=30,
|
|
56
|
+
text="Choropleth with Shadow Effect on Brazil",
|
|
57
|
+
fill="#267A8A",
|
|
58
|
+
fontWeight="bold",
|
|
59
|
+
fontFamily="Tangerine"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
viz.render_html(os.path.join(output_dir, "choropleth_effect.html"))
|
examples/json_export.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Example script for exporting the map configuration to JSON."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
12
|
+
try:
|
|
13
|
+
with open(data_path) as f:
|
|
14
|
+
world_data = json.load(f)
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
print(f"{data_path} not found.")
|
|
17
|
+
world_data = {}
|
|
18
|
+
|
|
19
|
+
viz = Geoviz(projection="EqualEarth")
|
|
20
|
+
viz.outline()
|
|
21
|
+
viz.graticule(stroke="white", strokeWidth=0.4)
|
|
22
|
+
viz.choro(
|
|
23
|
+
data=world_data,
|
|
24
|
+
var="gdppc",
|
|
25
|
+
strokeWidth=0.3,
|
|
26
|
+
tip=True,
|
|
27
|
+
leg_title="GDP per capita"
|
|
28
|
+
)
|
|
29
|
+
viz.header(text="Map from JSON Config", fontSize=30)
|
|
30
|
+
|
|
31
|
+
config_json = viz.to_json()
|
|
32
|
+
print("Configuration JSON (first 500 chars):")
|
|
33
|
+
print(config_json[:500] + "...")
|
|
34
|
+
|
|
35
|
+
with open(os.path.join(output_dir, "map_config.json"), "w") as f:
|
|
36
|
+
f.write(config_json)
|
|
37
|
+
print(f"\nConfiguration saved to {os.path.join(output_dir, 'map_config.json')}")
|
examples/labels.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Example script for adding text labels to a map."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
12
|
+
try:
|
|
13
|
+
with open(data_path) as f:
|
|
14
|
+
world_data = json.load(f)
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
print(f"{data_path} not found.")
|
|
17
|
+
world_data = {}
|
|
18
|
+
|
|
19
|
+
countries_to_label = ["Brazil", "China", "United States", "France", "South Africa", "Australia"]
|
|
20
|
+
labeled_features = [f for f in world_data.get("features", []) if f["properties"].get("NAMEen") in countries_to_label]
|
|
21
|
+
labeled_data = {"type": "FeatureCollection", "features": labeled_features}
|
|
22
|
+
|
|
23
|
+
viz = Geoviz(projection="EqualEarth")
|
|
24
|
+
viz.outline()
|
|
25
|
+
viz.graticule(stroke="white", strokeWidth=0.4)
|
|
26
|
+
|
|
27
|
+
viz.path(datum=world_data, fill="#e0e0e0", stroke="white")
|
|
28
|
+
|
|
29
|
+
viz.text(
|
|
30
|
+
data=labeled_data,
|
|
31
|
+
text="NAMEen",
|
|
32
|
+
fill="#333",
|
|
33
|
+
fontSize=14,
|
|
34
|
+
fontWeight="bold",
|
|
35
|
+
stroke="white",
|
|
36
|
+
strokeWidth=3,
|
|
37
|
+
paintOrder="stroke"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
viz.header(text="Map with Labels", fontSize=20)
|
|
43
|
+
viz.render_html(os.path.join(output_dir, "labels.html"))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Example script for layer control and export functionality."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
# Load world data
|
|
12
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
13
|
+
try:
|
|
14
|
+
with open(data_path) as f:
|
|
15
|
+
world_data = json.load(f)
|
|
16
|
+
except FileNotFoundError:
|
|
17
|
+
print(f"{data_path} not found.")
|
|
18
|
+
world_data = {}
|
|
19
|
+
|
|
20
|
+
# Create a map
|
|
21
|
+
viz = Geoviz(projection="EqualEarth", zoomable=True)
|
|
22
|
+
viz.outline(id="outline")
|
|
23
|
+
viz.graticule(stroke="white", strokeWidth=0.4, id="graticule")
|
|
24
|
+
|
|
25
|
+
# Add a choropleth layer with an ID and legend position
|
|
26
|
+
viz.choro(
|
|
27
|
+
data=world_data,
|
|
28
|
+
var="gdppc",
|
|
29
|
+
colors="Reds",
|
|
30
|
+
id="choropleth_gdp",
|
|
31
|
+
legend=True,
|
|
32
|
+
leg_title="GDP per Capita",
|
|
33
|
+
leg_pos=[10, 100] # Position for the choro legend
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Add a proportional symbol layer with an ID and legend position
|
|
37
|
+
viz.prop(
|
|
38
|
+
data=world_data,
|
|
39
|
+
var="pop",
|
|
40
|
+
fill="#4f8a8b",
|
|
41
|
+
stroke="white",
|
|
42
|
+
id="prop_population",
|
|
43
|
+
legend=True,
|
|
44
|
+
leg_title="Population",
|
|
45
|
+
leg_pos=[10, 250] # Position for the prop legend
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Add the layer control widget (hover to expand)
|
|
49
|
+
viz.add_layer_control(
|
|
50
|
+
layers=["choropleth_gdp", "prop_population", "graticule"],
|
|
51
|
+
title="Layers",
|
|
52
|
+
x=10,
|
|
53
|
+
y=10
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Add the export control widget (hover to expand)
|
|
57
|
+
viz.add_export_control(
|
|
58
|
+
pos="top-right" # Or use x, y
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Add opacity control for multiple layers
|
|
62
|
+
viz.add_opacity_control(
|
|
63
|
+
layers=["choropleth_gdp", "prop_population"],
|
|
64
|
+
title="Layers Opacity",
|
|
65
|
+
x=10,
|
|
66
|
+
y=90
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
viz.header(text="Map with Layer & Export Controls", fontSize=20)
|
|
70
|
+
viz.render_html(os.path.join(output_dir, "layer_control_map.html"))
|
examples/lines.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Example script demonstrating how to draw lines on a map."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
12
|
+
try:
|
|
13
|
+
with open(data_path) as f:
|
|
14
|
+
world_data = json.load(f)
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
print(f"{data_path} not found.")
|
|
17
|
+
world_data = {}
|
|
18
|
+
|
|
19
|
+
lines_data = {
|
|
20
|
+
"type": "FeatureCollection",
|
|
21
|
+
"features": [
|
|
22
|
+
{
|
|
23
|
+
"type": "Feature",
|
|
24
|
+
"properties": {"destination": "New York", "type": "Long-haul"},
|
|
25
|
+
"geometry": {
|
|
26
|
+
"type": "LineString",
|
|
27
|
+
"coordinates": [[2.35, 48.85], [-74.00, 40.71]]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "Feature",
|
|
32
|
+
"properties": {"destination": "Beijing", "type": "Long-haul"},
|
|
33
|
+
"geometry": {
|
|
34
|
+
"type": "LineString",
|
|
35
|
+
"coordinates": [[2.35, 48.85], [116.40, 39.90]]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "Feature",
|
|
40
|
+
"properties": {"destination": "Cape Town", "type": "Long-haul"},
|
|
41
|
+
"geometry": {
|
|
42
|
+
"type": "LineString",
|
|
43
|
+
"coordinates": [[2.35, 48.85], [18.42, -33.92]]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "Feature",
|
|
48
|
+
"properties": {"destination": "Moscow", "type": "Medium-haul"},
|
|
49
|
+
"geometry": {
|
|
50
|
+
"type": "LineString",
|
|
51
|
+
"coordinates": [[2.35, 48.85], [37.61, 55.75]]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
viz = Geoviz(projection="EqualEarth")
|
|
58
|
+
viz.outline()
|
|
59
|
+
viz.graticule(stroke="white", strokeWidth=0.4)
|
|
60
|
+
|
|
61
|
+
viz.path(datum=world_data, fill="#e0e0e0", stroke="white")
|
|
62
|
+
|
|
63
|
+
viz.typo(
|
|
64
|
+
data=lines_data,
|
|
65
|
+
var="destination",
|
|
66
|
+
colors="Set1",
|
|
67
|
+
strokeWidth=2,
|
|
68
|
+
fill="none",
|
|
69
|
+
leg_title="Routes from Paris",
|
|
70
|
+
leg_pos=[10, 250]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
viz.header(text="Map with Lines (Routes)", fontSize=20)
|
|
74
|
+
viz.render_html(os.path.join(output_dir, "lines.html"))
|
examples/reactive.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Example script for a reactive map (zoomable)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
12
|
+
try:
|
|
13
|
+
with open(data_path) as f:
|
|
14
|
+
world_data = json.load(f)
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
print(f"{data_path} not found.")
|
|
17
|
+
world_data = {}
|
|
18
|
+
|
|
19
|
+
viz = Geoviz(projection="polar", width=700, zoomable=True)
|
|
20
|
+
viz.outline()
|
|
21
|
+
viz.graticule(stroke="white", strokeWidth=0.4)
|
|
22
|
+
viz.path(datum=world_data, fill="white", fillOpacity=0.3)
|
|
23
|
+
|
|
24
|
+
viz.prop(
|
|
25
|
+
id="bubbles",
|
|
26
|
+
symbol="circle",
|
|
27
|
+
data=world_data,
|
|
28
|
+
var="pop",
|
|
29
|
+
fill="#F13C47",
|
|
30
|
+
dodge=False,
|
|
31
|
+
k=50,
|
|
32
|
+
leg_title="Population",
|
|
33
|
+
tip=True
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
print("Generating a static snapshot of the map.")
|
|
37
|
+
viz.render_html(os.path.join(output_dir, "reactive_snapshot.html"))
|
examples/simple.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Example script for a simple world map."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
# Load geojson
|
|
12
|
+
# Assuming running from repo root or examples folder
|
|
13
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
14
|
+
try:
|
|
15
|
+
with open(data_path) as f:
|
|
16
|
+
world_data = json.load(f)
|
|
17
|
+
except FileNotFoundError:
|
|
18
|
+
print(f"{data_path} not found.")
|
|
19
|
+
world_data = {}
|
|
20
|
+
|
|
21
|
+
viz = Geoviz(projection="EqualEarth")
|
|
22
|
+
viz.outline(fill="#267A8A")
|
|
23
|
+
viz.graticule(stroke="white", strokeWidth=0.4)
|
|
24
|
+
viz.path(
|
|
25
|
+
data=world_data,
|
|
26
|
+
fill="#F8D993",
|
|
27
|
+
stroke="#ada9a6",
|
|
28
|
+
strokeWidth=0.5,
|
|
29
|
+
tip="$NAMEen"
|
|
30
|
+
)
|
|
31
|
+
viz.header(
|
|
32
|
+
fontSize=30,
|
|
33
|
+
text="A Simple World Map",
|
|
34
|
+
fill="#267A8A",
|
|
35
|
+
fontWeight="bold",
|
|
36
|
+
fontFamily="Tangerine"
|
|
37
|
+
)
|
|
38
|
+
viz.render_html(os.path.join(output_dir, "simple.html"))
|
examples/tiles.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Example script demonstrating how to use tile layers."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from geovizpy import Geoviz
|
|
6
|
+
|
|
7
|
+
# Define output directory
|
|
8
|
+
output_dir = os.path.join(os.path.dirname(__file__), "html")
|
|
9
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
data_path = os.path.join(os.path.dirname(__file__), "data", "world.json")
|
|
12
|
+
try:
|
|
13
|
+
with open(data_path) as f:
|
|
14
|
+
world_data = json.load(f)
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
print(f"Data file not found: {data_path}")
|
|
17
|
+
world_data = {}
|
|
18
|
+
|
|
19
|
+
viz = Geoviz(projection="mercator", zoomable=True)
|
|
20
|
+
|
|
21
|
+
# Use the keyword for the tile layer, as per geoviz.js documentation
|
|
22
|
+
viz.tile(url="worldStreet")
|
|
23
|
+
|
|
24
|
+
viz.graticule(stroke="white", strokeWidth=0.4)
|
|
25
|
+
|
|
26
|
+
# This line might fail if world_data is empty
|
|
27
|
+
if world_data:
|
|
28
|
+
viz.prop(
|
|
29
|
+
data=world_data,
|
|
30
|
+
var="pop",
|
|
31
|
+
fill="#f07d75",
|
|
32
|
+
tip="$NAMEen",
|
|
33
|
+
leg_title="Population",
|
|
34
|
+
leg_frame=True,
|
|
35
|
+
leg_pos=[30, 30]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
viz.text(
|
|
39
|
+
pos=[0,0],
|
|
40
|
+
text="Source: INSEE 2022",
|
|
41
|
+
fontSize=10,
|
|
42
|
+
fill="#666",
|
|
43
|
+
anchor="end"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
viz.render_html(os.path.join(output_dir, "tiles.html"))
|
geovizpy/controls.py
CHANGED
|
@@ -29,3 +29,23 @@ class ControlsMixin:
|
|
|
29
29
|
"""
|
|
30
30
|
self.export_control_config = {"pos": pos, "x": x, "y": y, "title": title}
|
|
31
31
|
return self
|
|
32
|
+
|
|
33
|
+
def add_opacity_control(self, layers, pos=None, x=10, y=90, title="Opacity"):
|
|
34
|
+
"""
|
|
35
|
+
Add a slider to control the opacity of one or multiple layers.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
layers (string or list): The ID(s) of the layer(s) to control.
|
|
39
|
+
pos (string): Predefined position ("top-right", "top-left", etc.). Overrides x, y.
|
|
40
|
+
x (int): X position of the control.
|
|
41
|
+
y (int): Y position of the control.
|
|
42
|
+
title (string): Title of the control.
|
|
43
|
+
"""
|
|
44
|
+
if not hasattr(self, "opacity_control_configs"):
|
|
45
|
+
self.opacity_control_configs = []
|
|
46
|
+
|
|
47
|
+
if isinstance(layers, str):
|
|
48
|
+
layers = [layers]
|
|
49
|
+
|
|
50
|
+
self.opacity_control_configs.append({"layers": layers, "pos": pos, "x": x, "y": y, "title": title})
|
|
51
|
+
return self
|
geovizpy/geoviz.py
CHANGED
|
@@ -6,7 +6,6 @@ from .legends import LegendsMixin
|
|
|
6
6
|
from .effects import EffectsMixin
|
|
7
7
|
from .controls import ControlsMixin
|
|
8
8
|
from .renderer import RendererMixin
|
|
9
|
-
from .insets import InsetsMixin
|
|
10
9
|
|
|
11
10
|
class Geoviz(
|
|
12
11
|
MarksMixin,
|
|
@@ -14,8 +13,7 @@ class Geoviz(
|
|
|
14
13
|
LegendsMixin,
|
|
15
14
|
EffectsMixin,
|
|
16
15
|
ControlsMixin,
|
|
17
|
-
RendererMixin
|
|
18
|
-
InsetsMixin
|
|
16
|
+
RendererMixin
|
|
19
17
|
):
|
|
20
18
|
"""
|
|
21
19
|
A Python wrapper for the geoviz JavaScript library.
|
|
@@ -38,6 +36,7 @@ class Geoviz(
|
|
|
38
36
|
self.commands.append({"name": "create", "args": kwargs})
|
|
39
37
|
self.layer_control_config = None
|
|
40
38
|
self.export_control_config = None
|
|
39
|
+
self.opacity_control_configs = []
|
|
41
40
|
|
|
42
41
|
def _add_command(self, name, args):
|
|
43
42
|
"""Add a command to the list of commands to be executed."""
|
geovizpy/insets.py
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"""Module for creating inset maps."""
|
|
2
|
-
|
|
3
|
-
class InsetsMixin:
|
|
4
|
-
"""Mixin class for adding inset map functionality."""
|
|
5
|
-
|
|
6
|
-
def inset(self, **kwargs):
|
|
7
|
-
"""
|
|
8
|
-
Add an inset map to the main map.
|
|
9
|
-
|
|
10
|
-
Args:
|
|
11
|
-
pos (list): [x, y] position of the inset.
|
|
12
|
-
width (int): Width of the inset SVG.
|
|
13
|
-
height (int): Height of the inset SVG.
|
|
14
|
-
domain (object): GeoJSON to define the domain of the inset.
|
|
15
|
-
projection (string): Projection name for the inset.
|
|
16
|
-
outline (dict): Dictionary for outline properties (e.g., {"stroke": "black", "strokeWidth": 2}).
|
|
17
|
-
"""
|
|
18
|
-
# The actual creation of the inset will be handled in the JavaScript renderer,
|
|
19
|
-
# where the `geoviz.create` function will be called with the main SVG as parent.
|
|
20
|
-
return self._add_command("inset.create", kwargs)
|
geovizpy/renderer.py
CHANGED
|
@@ -18,7 +18,7 @@ class RendererMixin:
|
|
|
18
18
|
for k, v in args.items():
|
|
19
19
|
if v is None:
|
|
20
20
|
continue
|
|
21
|
-
if isinstance(v, str) and (v.strip().startswith("(") or v.strip().startswith("function") or "=>" in v):
|
|
21
|
+
if isinstance(v, str) and (v.strip().startswith("(") or v.strip().startswith("function") or "=>" in v or v.strip().startswith("d3.")):
|
|
22
22
|
new_args[k] = {"__js_func__": v}
|
|
23
23
|
elif isinstance(v, dict):
|
|
24
24
|
new_args[k] = process_args(v)
|
|
@@ -42,6 +42,7 @@ class RendererMixin:
|
|
|
42
42
|
|
|
43
43
|
layer_control_js = self._get_layer_control_js()
|
|
44
44
|
export_control_js = self._get_export_control_js()
|
|
45
|
+
opacity_control_js = self._get_opacity_control_js()
|
|
45
46
|
|
|
46
47
|
return f"""
|
|
47
48
|
<!DOCTYPE html>
|
|
@@ -58,6 +59,7 @@ class RendererMixin:
|
|
|
58
59
|
</style>
|
|
59
60
|
</head>
|
|
60
61
|
<body>
|
|
62
|
+
<div id="geoviz-container" style="position: relative; display: inline-block;"></div>
|
|
61
63
|
<script>
|
|
62
64
|
const commands = {json_commands};
|
|
63
65
|
let svg;
|
|
@@ -94,31 +96,23 @@ class RendererMixin:
|
|
|
94
96
|
}} else {{
|
|
95
97
|
console.warn("Method " + parts[0] + " not found");
|
|
96
98
|
}}
|
|
97
|
-
}} else if (parts.length === 2) {
|
|
98
|
-
if (
|
|
99
|
-
const insetArgs = { ...cmd.args };
|
|
100
|
-
const outlineArgs = insetArgs.outline;
|
|
101
|
-
delete insetArgs.outline;
|
|
102
|
-
|
|
103
|
-
const insetSvg = geoviz.create({ parent: svg, ...insetArgs });
|
|
104
|
-
if (outlineArgs) {
|
|
105
|
-
insetSvg.outline(outlineArgs);
|
|
106
|
-
}
|
|
107
|
-
} else if (svg[parts[0]] && svg[parts[0]][parts[1]]) {
|
|
99
|
+
}} else if (parts.length === 2) {{
|
|
100
|
+
if (svg[parts[0]] && svg[parts[0]][parts[1]]) {{
|
|
108
101
|
svg[parts[0]][parts[1]](cmd.args);
|
|
109
|
-
} else {
|
|
102
|
+
}} else {{
|
|
110
103
|
console.warn("Method " + cmd.name + " not found");
|
|
111
|
-
}
|
|
104
|
+
}}
|
|
112
105
|
}}
|
|
113
106
|
}}
|
|
114
107
|
}});
|
|
115
108
|
|
|
116
109
|
if (svg) {{
|
|
117
|
-
document.
|
|
110
|
+
document.getElementById("geoviz-container").appendChild(svg.render());
|
|
118
111
|
}}
|
|
119
112
|
|
|
120
113
|
{layer_control_js}
|
|
121
114
|
{export_control_js}
|
|
115
|
+
{opacity_control_js}
|
|
122
116
|
</script>
|
|
123
117
|
</body>
|
|
124
118
|
</html>
|
|
@@ -344,7 +338,7 @@ if __name__ == "__main__":
|
|
|
344
338
|
if (count > 0) {{
|
|
345
339
|
wrapper.appendChild(button);
|
|
346
340
|
wrapper.appendChild(panel);
|
|
347
|
-
document.
|
|
341
|
+
document.getElementById("geoviz-container").appendChild(wrapper);
|
|
348
342
|
}}
|
|
349
343
|
}}
|
|
350
344
|
setTimeout(createLayerControl, 100);
|
|
@@ -424,7 +418,127 @@ if __name__ == "__main__":
|
|
|
424
418
|
panel.appendChild(btnPNG);
|
|
425
419
|
wrapper.appendChild(button);
|
|
426
420
|
wrapper.appendChild(panel);
|
|
427
|
-
document.
|
|
421
|
+
document.getElementById("geoviz-container").appendChild(wrapper);
|
|
428
422
|
}}
|
|
429
423
|
setTimeout(createExportControl, 100);
|
|
430
424
|
"""
|
|
425
|
+
|
|
426
|
+
def _get_opacity_control_js(self):
|
|
427
|
+
if not hasattr(self, "opacity_control_configs") or not self.opacity_control_configs:
|
|
428
|
+
return ""
|
|
429
|
+
|
|
430
|
+
js_code = ""
|
|
431
|
+
for config in self.opacity_control_configs:
|
|
432
|
+
config_json = json.dumps(config)
|
|
433
|
+
js_code += f"""
|
|
434
|
+
(function() {{
|
|
435
|
+
const opacityConfig = {config_json};
|
|
436
|
+
|
|
437
|
+
function createOpacityControl() {{
|
|
438
|
+
const wrapper = document.createElement("div");
|
|
439
|
+
wrapper.style.position = "absolute";
|
|
440
|
+
wrapper.style.zIndex = "1000";
|
|
441
|
+
|
|
442
|
+
const button = document.createElement("div");
|
|
443
|
+
button.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="#333"><path d="M17.66 8L12 2.35 6.34 8C4.78 9.56 4 11.64 4 13.64s.78 4.11 2.34 5.66 3.61 2.35 5.66 2.35 4.1-.79 5.66-2.35S20 15.64 20 13.64 19.22 9.56 17.66 8zM6 14c.01-2 .62-3.27 1.76-4.4L12 5.27l4.24 4.38C17.38 10.77 17.99 12 18 14H6z"/></svg>`;
|
|
444
|
+
button.style.width = "32px";
|
|
445
|
+
button.style.height = "32px";
|
|
446
|
+
button.style.cursor = "pointer";
|
|
447
|
+
button.style.border = "1px solid #ccc";
|
|
448
|
+
button.style.borderRadius = "4px";
|
|
449
|
+
button.style.backgroundColor = "white";
|
|
450
|
+
button.style.display = "flex";
|
|
451
|
+
button.style.alignItems = "center";
|
|
452
|
+
button.style.justifyContent = "center";
|
|
453
|
+
button.style.boxShadow = "0 1px 3px rgba(0,0,0,0.2)";
|
|
454
|
+
|
|
455
|
+
const panel = document.createElement("div");
|
|
456
|
+
panel.style.display = "none";
|
|
457
|
+
panel.style.backgroundColor = "white";
|
|
458
|
+
panel.style.padding = "10px";
|
|
459
|
+
panel.style.border = "1px solid #ccc";
|
|
460
|
+
panel.style.borderRadius = "5px";
|
|
461
|
+
panel.style.fontFamily = "sans-serif";
|
|
462
|
+
panel.style.fontSize = "12px";
|
|
463
|
+
panel.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
|
|
464
|
+
panel.style.marginTop = "5px";
|
|
465
|
+
panel.style.minWidth = "150px";
|
|
466
|
+
|
|
467
|
+
wrapper.addEventListener("mouseenter", () => panel.style.display = "block");
|
|
468
|
+
wrapper.addEventListener("mouseleave", () => panel.style.display = "none");
|
|
469
|
+
|
|
470
|
+
if (opacityConfig.pos === "top-right") {{
|
|
471
|
+
wrapper.style.top = "90px"; wrapper.style.right = "10px";
|
|
472
|
+
}} else if (opacityConfig.pos === "bottom-right") {{
|
|
473
|
+
wrapper.style.bottom = "90px"; wrapper.style.right = "10px";
|
|
474
|
+
}} else {{
|
|
475
|
+
wrapper.style.top = `${{opacityConfig.y}}px`;
|
|
476
|
+
wrapper.style.left = `${{opacityConfig.x}}px`;
|
|
477
|
+
}}
|
|
478
|
+
|
|
479
|
+
const title = document.createElement("div");
|
|
480
|
+
title.innerText = opacityConfig.title;
|
|
481
|
+
title.style.fontWeight = "bold";
|
|
482
|
+
title.style.marginBottom = "8px";
|
|
483
|
+
title.style.borderBottom = "1px solid #eee";
|
|
484
|
+
title.style.paddingBottom = "5px";
|
|
485
|
+
panel.appendChild(title);
|
|
486
|
+
|
|
487
|
+
opacityConfig.layers.forEach(layerId => {{
|
|
488
|
+
const row = document.createElement("div");
|
|
489
|
+
row.style.marginBottom = "10px";
|
|
490
|
+
|
|
491
|
+
const label = document.createElement("div");
|
|
492
|
+
label.innerText = layerId;
|
|
493
|
+
label.style.fontSize = "11px";
|
|
494
|
+
label.style.marginBottom = "2px";
|
|
495
|
+
label.style.color = "#555";
|
|
496
|
+
|
|
497
|
+
const sliderContainer = document.createElement("div");
|
|
498
|
+
sliderContainer.style.display = "flex";
|
|
499
|
+
sliderContainer.style.alignItems = "center";
|
|
500
|
+
|
|
501
|
+
const slider = document.createElement("input");
|
|
502
|
+
slider.type = "range";
|
|
503
|
+
slider.min = "0";
|
|
504
|
+
slider.max = "1";
|
|
505
|
+
slider.step = "0.1";
|
|
506
|
+
slider.value = "1"; // Default value
|
|
507
|
+
slider.style.width = "100%";
|
|
508
|
+
slider.style.cursor = "pointer";
|
|
509
|
+
|
|
510
|
+
// Set initial slider value from layer's current opacity
|
|
511
|
+
const layerNodeForOpacity = svg.select("#" + layerId);
|
|
512
|
+
if (!layerNodeForOpacity.empty()) {{
|
|
513
|
+
const firstShape = layerNodeForOpacity.select("path, circle, rect");
|
|
514
|
+
if (!firstShape.empty()) {{
|
|
515
|
+
slider.value = firstShape.style("fill-opacity");
|
|
516
|
+
}}
|
|
517
|
+
}}
|
|
518
|
+
|
|
519
|
+
slider.addEventListener("input", (e) => {{
|
|
520
|
+
const val = e.target.value;
|
|
521
|
+
const layer = svg.select("#" + layerId);
|
|
522
|
+
if (!layer.empty()) {{
|
|
523
|
+
layer.selectAll("path, circle, rect").style("fill-opacity", val);
|
|
524
|
+
}}
|
|
525
|
+
const legLayer = svg.select("#leg_" + layerId);
|
|
526
|
+
if (!legLayer.empty()) {{
|
|
527
|
+
legLayer.selectAll("path, circle, rect").style("fill-opacity", val);
|
|
528
|
+
}}
|
|
529
|
+
}});
|
|
530
|
+
|
|
531
|
+
sliderContainer.appendChild(slider);
|
|
532
|
+
row.appendChild(label);
|
|
533
|
+
row.appendChild(sliderContainer);
|
|
534
|
+
panel.appendChild(row);
|
|
535
|
+
}});
|
|
536
|
+
|
|
537
|
+
wrapper.appendChild(button);
|
|
538
|
+
wrapper.appendChild(panel);
|
|
539
|
+
document.getElementById("geoviz-container").appendChild(wrapper);
|
|
540
|
+
}}
|
|
541
|
+
setTimeout(createOpacityControl, 100);
|
|
542
|
+
}})();
|
|
543
|
+
"""
|
|
544
|
+
return js_code
|
|
@@ -1,118 +1,112 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: geovizpy
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary: A Python wrapper for the geoviz JavaScript library
|
|
5
|
-
Author: fbxyz
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Classifier:
|
|
9
|
-
Classifier:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Dynamic:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
pip install geovizpy
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
playwright install
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# viz.save("my_map.png") # Renders a static PNG image (requires export dependencies)
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Documentation
|
|
117
|
-
|
|
118
|
-
For more detailed information on all available methods and parameters, please see the [full documentation](https://geovizpy.readthedocs.io/en/latest/).
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: geovizpy
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: A Python wrapper for the geoviz JavaScript library
|
|
5
|
+
Author: fbxyz
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Source, https://codeberg.org/fbxyz/geovizpy
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Provides-Extra: export
|
|
14
|
+
Requires-Dist: playwright; extra == "export"
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
|
|
17
|
+
# geovizpy
|
|
18
|
+
|
|
19
|
+
**geovizpy** is a Python wrapper for the `geoviz` JavaScript library, designed to bring the power of D3.js-based thematic mapping to Python. It allows you to create high-quality, interactive maps directly from Python scripts or Jupyter notebooks.
|
|
20
|
+
|
|
21
|
+
This library is a wrapper around the `geoviz` library. For detailed information on the underlying mapping logic, please refer to the [original geoviz documentation](https://github.com/neocarto/geoviz).
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **Simple, chainable API**: Build complex maps by chaining intuitive methods.
|
|
28
|
+
- **Variety of Map Types**: Create choropleth, proportional symbol, typology, and other thematic maps.
|
|
29
|
+
- **Interactive Controls**: Add hover-to-expand controls for toggling layer visibility and exporting the map as SVG or PNG.
|
|
30
|
+
- **Customizable**: Extensive options to customize colors, legends, strokes, and more.
|
|
31
|
+
- **Standalone HTML**: Renders self-contained HTML files with no server required.
|
|
32
|
+
- **Image Export**: Save maps directly to PNG or SVG from Python (requires optional dependencies).
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
### Standard Installation
|
|
37
|
+
|
|
38
|
+
You can install the core library using pip:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install geovizpy
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or install directly from the source repository:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install git+https://codeberg.org/fbxyz/geovizpy.git
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### For Image Export
|
|
51
|
+
|
|
52
|
+
To save maps as PNG or SVG files directly from Python, you need to install the optional `export` dependencies:
|
|
53
|
+
|
|
54
|
+
1. **Install the extra dependencies:**
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install "geovizpy[export]"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. **Install Playwright's browser binaries:**
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
playwright install
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
On Linux, you may also need to install host dependencies:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
sudo playwright install-deps
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
Here is a simple example of how to create a choropleth map:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from geovizpy import Geoviz
|
|
78
|
+
import json
|
|
79
|
+
|
|
80
|
+
# Load your GeoJSON data
|
|
81
|
+
# (Assuming 'world.json' is in a 'data' subdirectory)
|
|
82
|
+
with open("data/world.json") as f:
|
|
83
|
+
world_data = json.load(f)
|
|
84
|
+
|
|
85
|
+
# Initialize the map
|
|
86
|
+
viz = Geoviz(projection="EqualEarth", width=800)
|
|
87
|
+
|
|
88
|
+
# Add layers
|
|
89
|
+
viz.outline()
|
|
90
|
+
viz.graticule()
|
|
91
|
+
|
|
92
|
+
# Add a choropleth layer
|
|
93
|
+
viz.choro(
|
|
94
|
+
data=world_data,
|
|
95
|
+
var="gdppc",
|
|
96
|
+
colors="Blues",
|
|
97
|
+
legend=True,
|
|
98
|
+
leg_title="GDP per Capita"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Add interactive controls
|
|
102
|
+
viz.add_layer_control(layers=["choropleth_gdp"])
|
|
103
|
+
viz.add_export_control()
|
|
104
|
+
|
|
105
|
+
# Save the map
|
|
106
|
+
viz.save("my_map.html") # Renders an interactive HTML file
|
|
107
|
+
# viz.save("my_map.png") # Renders a static PNG image (requires export dependencies)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Documentation
|
|
111
|
+
|
|
112
|
+
For more detailed information on all available methods and parameters, please see the [full documentation](https://geovizpy.readthedocs.io/en/latest/).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
docs/source/conf.py,sha256=5ki8cc3YgZ76akTNYXTsnrnzWa-GGYDllwKHXji9J_I,815
|
|
2
|
+
examples/advanced_plots.py,sha256=1HlsJjYtyHMv1HvQwrSttB5c_5SlrXyQ7eX6c1XDbxc,2283
|
|
3
|
+
examples/bubble.py,sha256=N_ulY0hCj13XwQ4oWFt7ZFg5QmOAcp1JsZ_GCvrDHEg,1363
|
|
4
|
+
examples/choropleth.py,sha256=u9JgDyeckyIYw748R6kKgbjVQKpTknmlSBH98fY1j4Q,1062
|
|
5
|
+
examples/choropleth_effect.py,sha256=ReMWWSGi7pYNQ9cOsFO2rUgLdML27fdLSZt38rO_AmQ,1480
|
|
6
|
+
examples/json_export.py,sha256=MV5xvYrkPgIJRsDnk5iiRSN-DHVaVFbOtvuw3We0PVM,1024
|
|
7
|
+
examples/labels.py,sha256=Ql62vtqya-Dv8e-tDWhnSogJcG9Ku6A64WLIxm9mDJU,1194
|
|
8
|
+
examples/layer_control.py,sha256=D0dlqxFY_K9uGIfp4bbt_9ux5xisZzek8wN7Wj5aTXY,1771
|
|
9
|
+
examples/lines.py,sha256=sOrYm4d1lEGICSnU_22zc6g2aDgiL6-61506GJJXRWw,2064
|
|
10
|
+
examples/reactive.py,sha256=V6w9J-9yNEp52X5orUiH4ZQ3jQ8idAm4Z-VVfcwByOM,932
|
|
11
|
+
examples/simple.py,sha256=EP-iqlPZGCu5x7zBKXrhmOR18hC25mdZ1EpCGYoWMPM,936
|
|
12
|
+
examples/tiles.py,sha256=sUq7xDxd-ZO0mraQq98-oTCce3oasAlhfCo6CvkCmaw,1095
|
|
13
|
+
geovizpy/__init__.py,sha256=gvgVlK5oB6meBmYgXkXFVohPKkO3ev9UupPA8BQNzYg,120
|
|
14
|
+
geovizpy/controls.py,sha256=aFF60_ZcZ_NHVb1IeQJDRTmcqXXLk1oO-oLrr2eX7NI,2155
|
|
15
|
+
geovizpy/effects.py,sha256=6F78g0IJEbEG8EsKurmBapE8UX0sho7zKHDnzqJ0ci0,1433
|
|
16
|
+
geovizpy/geoviz.py,sha256=M_0_TisSDLvVy4lRDo1i3nBwVCoOcEXrMnt3xdr7k-o,1457
|
|
17
|
+
geovizpy/insets.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
geovizpy/legends.py,sha256=IPn_1drQ9MkmJlQ54XwBqAlTRSyq98sm8gzgE-ATRog,2218
|
|
19
|
+
geovizpy/marks.py,sha256=xqwf8ELY05lnrrXGQljMe-mm315vUt_-qOmdFAsYhwA,5520
|
|
20
|
+
geovizpy/plots.py,sha256=EVjPPBFyUFQpXH7sAuxCz_sctonJVUxoi8edbehKrpI,3089
|
|
21
|
+
geovizpy/renderer.py,sha256=BXiDLMh3pHFzMOtUPT6j-g9XdGhTkonZGr9UwY__Vmo,23198
|
|
22
|
+
geovizpy-0.1.8.dist-info/METADATA,sha256=hGc9qq-Kc-cHGjNtFbv2_f8F7fxMI8sM8P1gbEAJ1-A,3323
|
|
23
|
+
geovizpy-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
geovizpy-0.1.8.dist-info/top_level.txt,sha256=A35IMflZV2CCs6n081ygN9VFsh9z2zN3zghfZ_8fVmk,34
|
|
25
|
+
geovizpy-0.1.8.dist-info/RECORD,,
|
geovizpy-0.1.7.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
geovizpy/__init__.py,sha256=gvgVlK5oB6meBmYgXkXFVohPKkO3ev9UupPA8BQNzYg,120
|
|
2
|
-
geovizpy/controls.py,sha256=pvSrbrkgyB8VURVXED3Etou3Wk2wfWB5p4nnvHLFrgM,1319
|
|
3
|
-
geovizpy/effects.py,sha256=6F78g0IJEbEG8EsKurmBapE8UX0sho7zKHDnzqJ0ci0,1433
|
|
4
|
-
geovizpy/geoviz.py,sha256=nSkblpSf7MeHtdNhkC-g9ltByBaLCbIjW1URVizeMv0,1464
|
|
5
|
-
geovizpy/insets.py,sha256=ljraMXuPGhyPLfkkA9y7Io6fIQGD7j2dU44F9ZvM8pM,861
|
|
6
|
-
geovizpy/legends.py,sha256=IPn_1drQ9MkmJlQ54XwBqAlTRSyq98sm8gzgE-ATRog,2218
|
|
7
|
-
geovizpy/marks.py,sha256=xqwf8ELY05lnrrXGQljMe-mm315vUt_-qOmdFAsYhwA,5520
|
|
8
|
-
geovizpy/plots.py,sha256=EVjPPBFyUFQpXH7sAuxCz_sctonJVUxoi8edbehKrpI,3089
|
|
9
|
-
geovizpy/renderer.py,sha256=51jdjvR2uHr6fFeCOUHC1lCl8ayp7hU2yN5gD-f9rSg,16905
|
|
10
|
-
geovizpy-0.1.7.dist-info/METADATA,sha256=RxFZorl1X5yKz1CyiZu3egebipETy6BwjO0lQMqjCWE,3581
|
|
11
|
-
geovizpy-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
12
|
-
geovizpy-0.1.7.dist-info/top_level.txt,sha256=3D5AFdMd9bWEvGQUWDy6jccJamUwdKmchgCmxLHaAYs,9
|
|
13
|
-
geovizpy-0.1.7.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
geovizpy
|