일단 커밋. 오랫동안 커밋을 안해서 꼬였다.
리팩토리 중.
This commit is contained in:
13
.venv/lib/python3.12/site-packages/branca/__init__.py
Normal file
13
.venv/lib/python3.12/site-packages/branca/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import branca.colormap as colormap
|
||||
import branca.element as element
|
||||
|
||||
try:
|
||||
from ._version import __version__
|
||||
except ImportError:
|
||||
__version__ = "unknown"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"colormap",
|
||||
"element",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
.venv/lib/python3.12/site-packages/branca/_cnames.json
Normal file
1
.venv/lib/python3.12/site-packages/branca/_cnames.json
Normal file
@@ -0,0 +1 @@
|
||||
{"indigo": "#4B0082", "gold": "#FFD700", "hotpink": "#FF69B4", "firebrick": "#B22222", "indianred": "#CD5C5C", "sage": "#87AE73", "yellow": "#FFFF00", "mistyrose": "#FFE4E1", "darkolivegreen": "#556B2F", "olive": "#808000", "darkseagreen": "#8FBC8F", "pink": "#FFC0CB", "tomato": "#FF6347", "lightcoral": "#F08080", "orangered": "#FF4500", "navajowhite": "#FFDEAD", "lime": "#00FF00", "palegreen": "#98FB98", "greenyellow": "#ADFF2F", "burlywood": "#DEB887", "seashell": "#FFF5EE", "mediumspringgreen": "#00FA9A", "fuchsia": "#FF00FF", "papayawhip": "#FFEFD5", "blanchedalmond": "#FFEBCD", "chartreuse": "#7FFF00", "dimgray": "#696969", "black": "#000000", "peachpuff": "#FFDAB9", "springgreen": "#00FF7F", "aquamarine": "#7FFFD4", "white": "#FFFFFF", "b": "#0000FF", "orange": "#FFA500", "lightsalmon": "#FFA07A", "darkslategray": "#2F4F4F", "brown": "#A52A2A", "ivory": "#FFFFF0", "dodgerblue": "#1E90FF", "peru": "#CD853F", "lawngreen": "#7CFC00", "chocolate": "#D2691E", "crimson": "#DC143C", "forestgreen": "#228B22", "slateblue": "#6A5ACD", "lightseagreen": "#20B2AA", "cyan": "#00FFFF", "mintcream": "#F5FFFA", "silver": "#C0C0C0", "antiquewhite": "#FAEBD7", "mediumorchid": "#BA55D3", "skyblue": "#87CEEB", "gray": "#808080", "darkturquoise": "#00CED1", "goldenrod": "#DAA520", "darkgreen": "#006400", "floralwhite": "#FFFAF0", "darkviolet": "#9400D3", "darkgray": "#A9A9A9", "moccasin": "#FFE4B5", "saddlebrown": "#8B4513", "darkslateblue": "#483D8B", "lightskyblue": "#87CEFA", "lightpink": "#FFB6C1", "mediumvioletred": "#C71585", "r": "#FF0000", "red": "#FF0000", "deeppink": "#FF1493", "limegreen": "#32CD32", "k": "#000000", "darkmagenta": "#8B008B", "palegoldenrod": "#EEE8AA", "plum": "#DDA0DD", "turquoise": "#40E0D0", "m": "#FF00FF", "lightgoldenrodyellow": "#FAFAD2", "darkgoldenrod": "#B8860B", "lavender": "#E6E6FA", "maroon": "#800000", "yellowgreen": "#9ACD32", "sandybrown": "#FAA460", "thistle": "#D8BFD8", "violet": "#EE82EE", "navy": "#000080", "magenta": "#FF00FF", "tan": "#D2B48C", "rosybrown": "#BC8F8F", "olivedrab": "#6B8E23", "blue": "#0000FF", "lightblue": "#ADD8E6", "ghostwhite": "#F8F8FF", "honeydew": "#F0FFF0", "cornflowerblue": "#6495ED", "linen": "#FAF0E6", "darkblue": "#00008B", "powderblue": "#B0E0E6", "seagreen": "#2E8B57", "darkkhaki": "#BDB76B", "snow": "#FFFAFA", "sienna": "#A0522D", "mediumblue": "#0000CD", "royalblue": "#4169E1", "lightcyan": "#E0FFFF", "green": "#008000", "mediumpurple": "#9370DB", "midnightblue": "#191970", "cornsilk": "#FFF8DC", "paleturquoise": "#AFEEEE", "bisque": "#FFE4C4", "slategray": "#708090", "darkcyan": "#008B8B", "khaki": "#F0E68C", "wheat": "#F5DEB3", "teal": "#008080", "darkorchid": "#9932CC", "deepskyblue": "#00BFFF", "salmon": "#FA8072", "y": "#FFFF00", "darkred": "#8B0000", "steelblue": "#4682B4", "g": "#008000", "palevioletred": "#DB7093", "lightslategray": "#778899", "aliceblue": "#F0F8FF", "lightgreen": "#90EE90", "orchid": "#DA70D6", "gainsboro": "#DCDCDC", "mediumseagreen": "#3CB371", "lightgray": "#D3D3D3", "c": "#00FFFF", "mediumturquoise": "#48D1CC", "darksage": "#598556", "lemonchiffon": "#FFFACD", "cadetblue": "#5F9EA0", "lightyellow": "#FFFFE0", "lavenderblush": "#FFF0F5", "coral": "#FF7F50", "purple": "#800080", "aqua": "#00FFFF", "lightsage": "#BCECAC", "whitesmoke": "#F5F5F5", "mediumslateblue": "#7B68EE", "darkorange": "#FF8C00", "mediumaquamarine": "#66CDAA", "darksalmon": "#E9967A", "beige": "#F5F5DC", "w": "#FFFFFF", "blueviolet": "#8A2BE2", "azure": "#F0FFFF", "lightsteelblue": "#B0C4DE", "oldlace": "#FDF5E6"}
|
||||
1
.venv/lib/python3.12/site-packages/branca/_schemes.json
Normal file
1
.venv/lib/python3.12/site-packages/branca/_schemes.json
Normal file
File diff suppressed because one or more lines are too long
1
.venv/lib/python3.12/site-packages/branca/_version.py
Normal file
1
.venv/lib/python3.12/site-packages/branca/_version.py
Normal file
@@ -0,0 +1 @@
|
||||
__version__ = "0.8.2"
|
||||
674
.venv/lib/python3.12/site-packages/branca/colormap.py
Normal file
674
.venv/lib/python3.12/site-packages/branca/colormap.py
Normal file
@@ -0,0 +1,674 @@
|
||||
"""
|
||||
Colormap
|
||||
--------
|
||||
|
||||
Utility module for dealing with colormaps.
|
||||
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from typing import Dict, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
from jinja2 import Template
|
||||
|
||||
from branca.element import ENV, Figure, JavascriptLink, MacroElement
|
||||
from branca.utilities import legend_scaler
|
||||
|
||||
rootpath: str = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
with open(os.path.join(rootpath, "_cnames.json")) as f:
|
||||
_cnames: Dict[str, str] = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(rootpath, "_schemes.json")) as f:
|
||||
_schemes: Dict[str, List[str]] = json.loads(f.read())
|
||||
|
||||
|
||||
TypeRGBInts = Tuple[int, int, int]
|
||||
TypeRGBFloats = Tuple[float, float, float]
|
||||
TypeRGBAInts = Tuple[int, int, int, int]
|
||||
TypeRGBAFloats = Tuple[float, float, float, float]
|
||||
TypeAnyColorType = Union[TypeRGBInts, TypeRGBFloats, TypeRGBAInts, TypeRGBAFloats, str]
|
||||
|
||||
|
||||
def _is_hex(x: str) -> bool:
|
||||
return x.startswith("#") and len(x) == 7
|
||||
|
||||
|
||||
def _parse_hex(color_code: str) -> TypeRGBAFloats:
|
||||
return (
|
||||
_color_int_to_float(int(color_code[1:3], 16)),
|
||||
_color_int_to_float(int(color_code[3:5], 16)),
|
||||
_color_int_to_float(int(color_code[5:7], 16)),
|
||||
1.0,
|
||||
)
|
||||
|
||||
|
||||
def _color_int_to_float(x: int) -> float:
|
||||
"""Convert an integer between 0 and 255 to a float between 0. and 1.0"""
|
||||
return x / 255.0
|
||||
|
||||
|
||||
def _color_float_to_int(x: float) -> int:
|
||||
"""Convert a float between 0. and 1.0 to an integer between 0 and 255"""
|
||||
return int(x * 255.9999)
|
||||
|
||||
|
||||
def _parse_color(x: Union[tuple, list, str]) -> TypeRGBAFloats:
|
||||
if isinstance(x, (tuple, list)):
|
||||
return tuple(tuple(x) + (1.0,))[:4] # type: ignore
|
||||
elif isinstance(x, str) and _is_hex(x):
|
||||
return _parse_hex(x)
|
||||
elif isinstance(x, str):
|
||||
cname = _cnames.get(x.lower(), None)
|
||||
if cname is None:
|
||||
raise ValueError(f"Unknown color {cname!r}.")
|
||||
return _parse_hex(cname)
|
||||
else:
|
||||
raise ValueError(f"Unrecognized color code {x!r}")
|
||||
|
||||
|
||||
def _base(x: float) -> float:
|
||||
if x > 0:
|
||||
base = pow(10, math.floor(math.log10(x)))
|
||||
return round(x / base) * base
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
class ColorMap(MacroElement):
|
||||
"""A generic class for creating colormaps.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vmin: float
|
||||
The left bound of the color scale.
|
||||
vmax: float
|
||||
The right bound of the color scale.
|
||||
caption: str
|
||||
A caption to draw with the colormap.
|
||||
text_color: str, default "black"
|
||||
The color for the text.
|
||||
max_labels : int, default 10
|
||||
Maximum number of legend tick labels
|
||||
"""
|
||||
|
||||
_template: Template = ENV.get_template("color_scale.js")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vmin: float = 0.0,
|
||||
vmax: float = 1.0,
|
||||
caption: str = "",
|
||||
text_color: str = "black",
|
||||
max_labels: int = 10,
|
||||
):
|
||||
super().__init__()
|
||||
self._name = "ColorMap"
|
||||
|
||||
self.vmin = vmin
|
||||
self.vmax = vmax
|
||||
self.caption = caption
|
||||
self.text_color = text_color
|
||||
self.index: List[float] = [vmin, vmax]
|
||||
self.max_labels = max_labels
|
||||
self.tick_labels: Optional[Sequence[Union[float, str]]] = None
|
||||
|
||||
self.width = 450
|
||||
self.height = 40
|
||||
|
||||
def render(self, **kwargs):
|
||||
"""Renders the HTML representation of the element."""
|
||||
self.color_domain = [
|
||||
float(self.vmin + (self.vmax - self.vmin) * k / 499.0) for k in range(500)
|
||||
]
|
||||
self.color_range = [self.__call__(x) for x in self.color_domain]
|
||||
|
||||
# sanitize possible numpy floats to native python floats
|
||||
self.index = [float(i) for i in self.index]
|
||||
|
||||
if self.tick_labels is None:
|
||||
self.tick_labels = legend_scaler(self.index, self.max_labels)
|
||||
|
||||
super().render(**kwargs)
|
||||
|
||||
figure = self.get_root()
|
||||
assert isinstance(figure, Figure), (
|
||||
"You cannot render this Element " "if it is not in a Figure."
|
||||
)
|
||||
|
||||
figure.header.add_child(
|
||||
JavascriptLink("https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"),
|
||||
name="d3",
|
||||
) # noqa
|
||||
|
||||
def rgba_floats_tuple(self, x: float) -> TypeRGBAFloats:
|
||||
"""
|
||||
This class has to be implemented for each class inheriting from
|
||||
Colormap. This has to be a function of the form float ->
|
||||
(float, float, float, float) describing for each input float x,
|
||||
the output color in RGBA format;
|
||||
Each output value being between 0 and 1.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def rgba_bytes_tuple(self, x: float) -> TypeRGBAInts:
|
||||
"""Provides the color corresponding to value `x` in the
|
||||
form of a tuple (R,G,B,A) with int values between 0 and 255.
|
||||
"""
|
||||
return tuple(_color_float_to_int(u) for u in self.rgba_floats_tuple(x)) # type: ignore
|
||||
|
||||
def rgb_bytes_tuple(self, x: float) -> TypeRGBInts:
|
||||
"""Provides the color corresponding to value `x` in the
|
||||
form of a tuple (R,G,B) with int values between 0 and 255.
|
||||
"""
|
||||
return self.rgba_bytes_tuple(x)[:3]
|
||||
|
||||
def rgb_hex_str(self, x: float) -> str:
|
||||
"""Provides the color corresponding to value `x` in the
|
||||
form of a string of hexadecimal values "#RRGGBB".
|
||||
"""
|
||||
return "#%02x%02x%02x" % self.rgb_bytes_tuple(x)
|
||||
|
||||
def rgba_hex_str(self, x: float) -> str:
|
||||
"""Provides the color corresponding to value `x` in the
|
||||
form of a string of hexadecimal values "#RRGGBBAA".
|
||||
"""
|
||||
return "#%02x%02x%02x%02x" % self.rgba_bytes_tuple(x)
|
||||
|
||||
def __call__(self, x: float) -> str:
|
||||
"""Provides the color corresponding to value `x` in the
|
||||
form of a string of hexadecimal values "#RRGGBBAA".
|
||||
"""
|
||||
return self.rgba_hex_str(x)
|
||||
|
||||
def _repr_html_(self) -> str:
|
||||
"""Display the colormap in a Jupyter Notebook.
|
||||
|
||||
Does not support all the class arguments.
|
||||
|
||||
"""
|
||||
nb_ticks = 7
|
||||
delta_x = math.floor(self.width / (nb_ticks - 1))
|
||||
x_ticks = [(i) * delta_x for i in range(0, nb_ticks)]
|
||||
delta_val = delta_x * (self.vmax - self.vmin) / self.width
|
||||
val_ticks = [round(self.vmin + (i) * delta_val, 1) for i in range(0, nb_ticks)]
|
||||
|
||||
return (
|
||||
f'<svg height="40" width="{self.width}">'
|
||||
+ "".join(
|
||||
[
|
||||
(
|
||||
'<line x1="{i}" y1="15" x2="{i}" '
|
||||
'y2="27" style="stroke:{color};stroke-width:2;" />'
|
||||
).format(
|
||||
i=i * 1,
|
||||
color=self.rgba_hex_str(
|
||||
self.vmin + (self.vmax - self.vmin) * i / (self.width - 1),
|
||||
),
|
||||
)
|
||||
for i in range(self.width)
|
||||
],
|
||||
)
|
||||
+ (
|
||||
'<text x="0" y="38" style="text-anchor:start; font-size:11px;'
|
||||
' font:Arial; fill:{}">{}</text>'
|
||||
).format(
|
||||
self.text_color,
|
||||
self.vmin,
|
||||
)
|
||||
+ "".join(
|
||||
[
|
||||
(
|
||||
'<text x="{}" y="38"; style="text-anchor:middle; font-size:11px;'
|
||||
' font:Arial; fill:{}">{}</text>'
|
||||
).format(x_ticks[i], self.text_color, val_ticks[i])
|
||||
for i in range(1, nb_ticks - 1)
|
||||
],
|
||||
)
|
||||
+ (
|
||||
'<text x="{}" y="38" style="text-anchor:end; font-size:11px;'
|
||||
' font:Arial; fill:{}">{}</text>'
|
||||
).format(
|
||||
self.width,
|
||||
self.text_color,
|
||||
self.vmax,
|
||||
)
|
||||
+ '<text x="0" y="12" style="font-size:11px; font:Arial; fill:{}">{}</text>'.format(
|
||||
self.text_color,
|
||||
self.caption,
|
||||
)
|
||||
+ "</svg>"
|
||||
)
|
||||
|
||||
|
||||
class LinearColormap(ColorMap):
|
||||
"""Creates a ColorMap based on linear interpolation of a set of colors
|
||||
over a given index.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
colors : list-like object with at least two colors.
|
||||
The set of colors to be used for interpolation.
|
||||
Colors can be provided in the form:
|
||||
* tuples of RGBA ints between 0 and 255 (e.g: `(255, 255, 0)` or
|
||||
`(255, 255, 0, 255)`)
|
||||
* tuples of RGBA floats between 0. and 1. (e.g: `(1.,1.,0.)` or
|
||||
`(1., 1., 0., 1.)`)
|
||||
* HTML-like string (e.g: `"#ffff00`)
|
||||
* a color name or shortcut (e.g: `"y"` or `"yellow"`)
|
||||
index : list of floats, default None
|
||||
The values corresponding to each color.
|
||||
It has to be sorted, and have the same length as `colors`.
|
||||
If None, a regular grid between `vmin` and `vmax` is created.
|
||||
vmin : float, default 0.
|
||||
The minimal value for the colormap.
|
||||
Values lower than `vmin` will be bound directly to `colors[0]`.
|
||||
vmax : float, default 1.
|
||||
The maximal value for the colormap.
|
||||
Values higher than `vmax` will be bound directly to `colors[-1]`.
|
||||
caption: str
|
||||
A caption to draw with the colormap.
|
||||
text_color: str, default "black"
|
||||
The color for the text.
|
||||
max_labels : int, default 10
|
||||
Maximum number of legend tick labels
|
||||
tick_labels: list of floats, default None
|
||||
If given, used as the positions of ticks."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
colors: Sequence[TypeAnyColorType],
|
||||
index: Optional[Sequence[float]] = None,
|
||||
vmin: float = 0.0,
|
||||
vmax: float = 1.0,
|
||||
caption: str = "",
|
||||
text_color: str = "black",
|
||||
max_labels: int = 10,
|
||||
tick_labels: Optional[Sequence[float]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
vmin=vmin,
|
||||
vmax=vmax,
|
||||
caption=caption,
|
||||
text_color=text_color,
|
||||
max_labels=max_labels,
|
||||
)
|
||||
self.tick_labels: Optional[Sequence[float]] = tick_labels
|
||||
|
||||
n = len(colors)
|
||||
if n < 2:
|
||||
raise ValueError("You must provide at least 2 colors.")
|
||||
if index is None:
|
||||
self.index = [vmin + (vmax - vmin) * i * 1.0 / (n - 1) for i in range(n)]
|
||||
else:
|
||||
self.index = list(index)
|
||||
self.colors: List[TypeRGBAFloats] = [_parse_color(x) for x in colors]
|
||||
|
||||
def rgba_floats_tuple(self, x: float) -> TypeRGBAFloats:
|
||||
"""Provides the color corresponding to value `x` in the
|
||||
form of a tuple (R,G,B,A) with float values between 0. and 1.
|
||||
"""
|
||||
if x <= self.index[0]:
|
||||
return self.colors[0]
|
||||
if x >= self.index[-1]:
|
||||
return self.colors[-1]
|
||||
|
||||
i = len([u for u in self.index if u < x]) # 0 < i < n.
|
||||
if self.index[i - 1] < self.index[i]:
|
||||
p = (x - self.index[i - 1]) * 1.0 / (self.index[i] - self.index[i - 1])
|
||||
elif self.index[i - 1] == self.index[i]:
|
||||
p = 1.0
|
||||
else:
|
||||
raise ValueError("Thresholds are not sorted.")
|
||||
|
||||
return tuple( # type: ignore
|
||||
(1.0 - p) * self.colors[i - 1][j] + p * self.colors[i][j] for j in range(4)
|
||||
)
|
||||
|
||||
def to_step(
|
||||
self,
|
||||
n: Optional[int] = None,
|
||||
index: Optional[Sequence[float]] = None,
|
||||
data: Optional[Sequence[float]] = None,
|
||||
method: str = "linear",
|
||||
quantiles: Optional[Sequence[float]] = None,
|
||||
round_method: Optional[str] = None,
|
||||
max_labels: int = 10,
|
||||
) -> "StepColormap":
|
||||
"""Splits the LinearColormap into a StepColormap.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n : int, default None
|
||||
The number of expected colors in the output StepColormap.
|
||||
This will be ignored if `index` is provided.
|
||||
index : list of floats, default None
|
||||
The values corresponding to each color bounds.
|
||||
It has to be sorted.
|
||||
If None, a regular grid between `vmin` and `vmax` is created.
|
||||
data : list of floats, default None
|
||||
A sample of data to adapt the color map to.
|
||||
method : str, default 'linear'
|
||||
The method used to create data-based colormap.
|
||||
It can be 'linear' for linear scale, 'log' for logarithmic,
|
||||
or 'quant' for data's quantile-based scale.
|
||||
quantiles : list of floats, default None
|
||||
Alternatively, you can provide explicitly the quantiles you
|
||||
want to use in the scale.
|
||||
round_method : str, default None
|
||||
The method used to round thresholds.
|
||||
* If 'int', all values will be rounded to the nearest integer.
|
||||
* If 'log10', all values will be rounded to the nearest
|
||||
order-of-magnitude integer. For example, 2100 is rounded to
|
||||
2000, 2790 to 3000.
|
||||
max_labels : int, default 10
|
||||
Maximum number of legend tick labels
|
||||
|
||||
Returns
|
||||
-------
|
||||
A StepColormap with `n=len(index)-1` colors.
|
||||
|
||||
Examples:
|
||||
>> lc.to_step(n=12)
|
||||
>> lc.to_step(index=[0, 2, 4, 6, 8, 10])
|
||||
>> lc.to_step(data=some_list, n=12)
|
||||
>> lc.to_step(data=some_list, n=12, method='linear')
|
||||
>> lc.to_step(data=some_list, n=12, method='log')
|
||||
>> lc.to_step(data=some_list, n=12, method='quantiles')
|
||||
>> lc.to_step(data=some_list, quantiles=[0, 0.3, 0.7, 1])
|
||||
>> lc.to_step(data=some_list, quantiles=[0, 0.3, 0.7, 1],
|
||||
... round_method='log10')
|
||||
|
||||
"""
|
||||
msg = "You must specify either `index` or `n`"
|
||||
if index is None:
|
||||
if data is None:
|
||||
if n is None:
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
index = [
|
||||
self.vmin + (self.vmax - self.vmin) * i * 1.0 / n
|
||||
for i in range(1 + n)
|
||||
]
|
||||
scaled_cm = self
|
||||
else:
|
||||
max_ = max(data)
|
||||
min_ = min(data)
|
||||
scaled_cm = self.scale(vmin=min_, vmax=max_)
|
||||
method = "quantiles" if quantiles is not None else method
|
||||
if method.lower().startswith("lin"):
|
||||
if n is None:
|
||||
raise ValueError(msg)
|
||||
index = [min_ + i * (max_ - min_) * 1.0 / n for i in range(1 + n)]
|
||||
elif method.lower().startswith("log"):
|
||||
if n is None:
|
||||
raise ValueError(msg)
|
||||
if min_ <= 0:
|
||||
msg = "Log-scale works only with strictly " "positive values."
|
||||
raise ValueError(msg)
|
||||
index = [
|
||||
math.exp(
|
||||
math.log(min_)
|
||||
+ i * (math.log(max_) - math.log(min_)) * 1.0 / n,
|
||||
)
|
||||
for i in range(1 + n)
|
||||
]
|
||||
elif method.lower().startswith("quant"):
|
||||
if quantiles is None:
|
||||
if n is None:
|
||||
msg = (
|
||||
"You must specify either `index`, `n` or" "`quantiles`."
|
||||
)
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
quantiles = [i * 1.0 / n for i in range(1 + n)]
|
||||
p = len(data) - 1
|
||||
s = sorted(data)
|
||||
index = [
|
||||
s[int(q * p)] * (1.0 - (q * p) % 1)
|
||||
+ s[min(int(q * p) + 1, p)] * ((q * p) % 1)
|
||||
for q in quantiles
|
||||
]
|
||||
else:
|
||||
raise ValueError(f"Unknown method {method}")
|
||||
else:
|
||||
scaled_cm = self.scale(vmin=min(index), vmax=max(index))
|
||||
|
||||
n = len(index) - 1
|
||||
|
||||
if round_method == "int":
|
||||
index = [round(x) for x in index]
|
||||
|
||||
if round_method == "log10":
|
||||
index = [_base(x) for x in index]
|
||||
|
||||
colors = [
|
||||
scaled_cm.rgba_floats_tuple(
|
||||
index[i] * (1.0 - i / (n - 1.0)) + index[i + 1] * i / (n - 1.0),
|
||||
)
|
||||
for i in range(n)
|
||||
]
|
||||
|
||||
caption = self.caption
|
||||
text_color = self.text_color
|
||||
|
||||
return StepColormap(
|
||||
colors,
|
||||
index=index,
|
||||
vmin=index[0],
|
||||
vmax=index[-1],
|
||||
caption=caption,
|
||||
text_color=text_color,
|
||||
max_labels=max_labels,
|
||||
tick_labels=self.tick_labels,
|
||||
)
|
||||
|
||||
def scale(
|
||||
self,
|
||||
vmin: float = 0.0,
|
||||
vmax: float = 1.0,
|
||||
max_labels: int = 10,
|
||||
) -> "LinearColormap":
|
||||
"""Transforms the colorscale so that the minimal and maximal values
|
||||
fit the given parameters.
|
||||
"""
|
||||
return LinearColormap(
|
||||
self.colors,
|
||||
index=[
|
||||
vmin + (vmax - vmin) * (x - self.vmin) * 1.0 / (self.vmax - self.vmin)
|
||||
for x in self.index
|
||||
], # noqa
|
||||
vmin=vmin,
|
||||
vmax=vmax,
|
||||
caption=self.caption,
|
||||
text_color=self.text_color,
|
||||
max_labels=max_labels,
|
||||
)
|
||||
|
||||
|
||||
class StepColormap(ColorMap):
|
||||
"""Creates a ColorMap based on linear interpolation of a set of colors
|
||||
over a given index.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
colors : list-like object
|
||||
The set of colors to be used for interpolation.
|
||||
Colors can be provided in the form:
|
||||
* tuples of int between 0 and 255 (e.g: `(255,255,0)` or
|
||||
`(255, 255, 0, 255)`)
|
||||
* tuples of floats between 0. and 1. (e.g: `(1.,1.,0.)` or
|
||||
`(1., 1., 0., 1.)`)
|
||||
* HTML-like string (e.g: `"#ffff00`)
|
||||
* a color name or shortcut (e.g: `"y"` or `"yellow"`)
|
||||
index : list of floats, default None
|
||||
The bounds of the colors. The lower value is inclusive,
|
||||
the upper value is exclusive.
|
||||
It has to be sorted, and have the same length as `colors`.
|
||||
If None, a regular grid between `vmin` and `vmax` is created.
|
||||
vmin : float, default 0.
|
||||
The minimal value for the colormap.
|
||||
Values lower than `vmin` will be bound directly to `colors[0]`.
|
||||
vmax : float, default 1.
|
||||
The maximal value for the colormap.
|
||||
Values higher than `vmax` will be bound directly to `colors[-1]`.
|
||||
caption: str
|
||||
A caption to draw with the colormap.
|
||||
text_color: str, default "black"
|
||||
The color for the text.
|
||||
max_labels : int, default 10
|
||||
Maximum number of legend tick labels
|
||||
tick_labels: list of floats, default None
|
||||
If given, used as the positions of ticks.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
colors: Sequence[TypeAnyColorType],
|
||||
index: Optional[Sequence[float]] = None,
|
||||
vmin: float = 0.0,
|
||||
vmax: float = 1.0,
|
||||
caption: str = "",
|
||||
text_color: str = "black",
|
||||
max_labels: int = 10,
|
||||
tick_labels: Optional[Sequence[float]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
vmin=vmin,
|
||||
vmax=vmax,
|
||||
caption=caption,
|
||||
text_color=text_color,
|
||||
max_labels=max_labels,
|
||||
)
|
||||
self.tick_labels = tick_labels
|
||||
|
||||
n = len(colors)
|
||||
if n < 1:
|
||||
raise ValueError("You must provide at least 1 colors.")
|
||||
if index is None:
|
||||
self.index = [vmin + (vmax - vmin) * i * 1.0 / n for i in range(n + 1)]
|
||||
else:
|
||||
self.index = list(index)
|
||||
self.colors: List[TypeRGBAFloats] = [_parse_color(x) for x in colors]
|
||||
|
||||
def rgba_floats_tuple(self, x: float) -> TypeRGBAFloats:
|
||||
"""
|
||||
Provides the color corresponding to value `x` in the
|
||||
form of a tuple (R,G,B,A) with float values between 0. and 1.
|
||||
|
||||
"""
|
||||
if x <= self.index[0]:
|
||||
return self.colors[0]
|
||||
if x >= self.index[-1]:
|
||||
return self.colors[-1]
|
||||
|
||||
i = len([u for u in self.index if u <= x]) # 0 < i < n.
|
||||
return self.colors[i - 1]
|
||||
|
||||
def to_linear(
|
||||
self,
|
||||
index: Optional[Sequence[float]] = None,
|
||||
max_labels: int = 10,
|
||||
) -> LinearColormap:
|
||||
"""
|
||||
Transforms the StepColormap into a LinearColormap.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index : list of floats, default None
|
||||
The values corresponding to each color in the output colormap.
|
||||
It has to be sorted.
|
||||
If None, a regular grid between `vmin` and `vmax` is created.
|
||||
max_labels : int, default 10
|
||||
Maximum number of legend tick labels
|
||||
|
||||
"""
|
||||
if index is None:
|
||||
n = len(self.index) - 1
|
||||
index = [
|
||||
self.index[i] * (1.0 - i / (n - 1.0))
|
||||
+ self.index[i + 1] * i / (n - 1.0)
|
||||
for i in range(n)
|
||||
]
|
||||
|
||||
colors = [self.rgba_floats_tuple(x) for x in index]
|
||||
return LinearColormap(
|
||||
colors,
|
||||
index=index,
|
||||
vmin=self.vmin,
|
||||
vmax=self.vmax,
|
||||
caption=self.caption,
|
||||
text_color=self.text_color,
|
||||
max_labels=max_labels,
|
||||
)
|
||||
|
||||
def scale(
|
||||
self,
|
||||
vmin: float = 0.0,
|
||||
vmax: float = 1.0,
|
||||
max_labels: int = 10,
|
||||
) -> "StepColormap":
|
||||
"""Transforms the colorscale so that the minimal and maximal values
|
||||
fit the given parameters.
|
||||
"""
|
||||
return StepColormap(
|
||||
self.colors,
|
||||
index=[
|
||||
vmin + (vmax - vmin) * (x - self.vmin) * 1.0 / (self.vmax - self.vmin)
|
||||
for x in self.index
|
||||
], # noqa
|
||||
vmin=vmin,
|
||||
vmax=vmax,
|
||||
caption=self.caption,
|
||||
text_color=self.text_color,
|
||||
max_labels=max_labels,
|
||||
)
|
||||
|
||||
|
||||
class _LinearColormaps:
|
||||
"""A class for hosting the list of built-in linear colormaps."""
|
||||
|
||||
def __init__(self):
|
||||
self._schemes = _schemes.copy()
|
||||
self._colormaps = {key: LinearColormap(val) for key, val in _schemes.items()}
|
||||
for key, val in _schemes.items():
|
||||
setattr(self, key, LinearColormap(val))
|
||||
|
||||
def _repr_html_(self) -> str:
|
||||
return Template(
|
||||
"""
|
||||
<table>
|
||||
{% for key,val in this._colormaps.items() %}
|
||||
<tr><td>{{key}}</td><td>{{val._repr_html_()}}</td></tr>
|
||||
{% endfor %}</table>
|
||||
""",
|
||||
).render(this=self)
|
||||
|
||||
|
||||
linear = _LinearColormaps()
|
||||
|
||||
|
||||
class _StepColormaps:
|
||||
"""A class for hosting the list of built-in step colormaps."""
|
||||
|
||||
def __init__(self):
|
||||
self._schemes = _schemes.copy()
|
||||
self._colormaps = {key: StepColormap(val) for key, val in _schemes.items()}
|
||||
for key, val in _schemes.items():
|
||||
setattr(self, key, StepColormap(val))
|
||||
|
||||
def _repr_html_(self) -> str:
|
||||
return Template(
|
||||
"""
|
||||
<table>
|
||||
{% for key,val in this._colormaps.items() %}
|
||||
<tr><td>{{key}}</td><td>{{val._repr_html_()}}</td></tr>
|
||||
{% endfor %}</table>
|
||||
""",
|
||||
).render(this=self)
|
||||
|
||||
|
||||
step = _StepColormaps()
|
||||
740
.venv/lib/python3.12/site-packages/branca/element.py
Normal file
740
.venv/lib/python3.12/site-packages/branca/element.py
Normal file
@@ -0,0 +1,740 @@
|
||||
"""
|
||||
Element
|
||||
-------
|
||||
|
||||
A generic class for creating Elements.
|
||||
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import warnings
|
||||
from binascii import hexlify
|
||||
from collections import OrderedDict
|
||||
from html import escape
|
||||
from os import urandom
|
||||
from pathlib import Path
|
||||
from typing import BinaryIO, List, Optional, Tuple, Type, Union
|
||||
from urllib.request import urlopen
|
||||
|
||||
from jinja2 import Environment, PackageLoader, Template
|
||||
|
||||
from .utilities import TypeParseSize, _camelify, _parse_size, none_max, none_min
|
||||
|
||||
ENV = Environment(loader=PackageLoader("branca", "templates"))
|
||||
|
||||
|
||||
class Element:
|
||||
"""Basic Element object that does nothing.
|
||||
Other Elements may inherit from this one.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
template : str, default None
|
||||
A jinaj2-compatible template string for rendering the element.
|
||||
If None, template will be:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% for name, element in this._children.items() %}
|
||||
{{element.render(**kwargs)}}
|
||||
{% endfor %}
|
||||
|
||||
so that all the element's children are rendered.
|
||||
template_name : str, default None
|
||||
If no template is provided, you can also provide a filename.
|
||||
|
||||
"""
|
||||
|
||||
_template: Template = Template(
|
||||
"{% for name, element in this._children.items() %}\n"
|
||||
" {{element.render(**kwargs)}}"
|
||||
"{% endfor %}",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
template: Optional[str] = None,
|
||||
template_name: Optional[str] = None,
|
||||
):
|
||||
self._name: str = "Element"
|
||||
self._id: str = self._generate_id()
|
||||
self._children: OrderedDict[str, Element] = OrderedDict()
|
||||
self._parent: Optional[Element] = None
|
||||
self._template_str: Optional[str] = template
|
||||
self._template_name: Optional[str] = template_name
|
||||
|
||||
if template is not None:
|
||||
self._template = Template(template)
|
||||
elif template_name is not None:
|
||||
self._template = ENV.get_template(template_name)
|
||||
|
||||
@classmethod
|
||||
def _generate_id(cls) -> str:
|
||||
return hexlify(urandom(16)).decode()
|
||||
|
||||
def __getstate__(self) -> dict:
|
||||
"""Modify object state when pickling the object.
|
||||
|
||||
jinja2 Templates cannot be pickled, so remove the instance attribute
|
||||
if it exists. It will be added back when unpickling (see __setstate__).
|
||||
"""
|
||||
state: dict = self.__dict__.copy()
|
||||
state.pop("_template", None)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state: dict):
|
||||
"""Re-add _template instance attribute when unpickling"""
|
||||
if state["_template_str"] is not None:
|
||||
state["_template"] = Template(state["_template_str"])
|
||||
elif state["_template_name"] is not None:
|
||||
state["_template"] = ENV.get_template(state["_template_name"])
|
||||
|
||||
self.__dict__.update(state)
|
||||
|
||||
def get_name(self) -> str:
|
||||
"""Returns a string representation of the object.
|
||||
This string has to be unique and to be a python and
|
||||
javascript-compatible
|
||||
variable name.
|
||||
"""
|
||||
return _camelify(self._name) + "_" + self._id
|
||||
|
||||
def _get_self_bounds(self) -> List[List[Optional[float]]]:
|
||||
"""Computes the bounds of the object itself (not including it's children)
|
||||
in the form [[lat_min, lon_min], [lat_max, lon_max]]
|
||||
"""
|
||||
return [[None, None], [None, None]]
|
||||
|
||||
def get_bounds(self) -> List[List[Optional[float]]]:
|
||||
"""Computes the bounds of the object and all it's children
|
||||
in the form [[lat_min, lon_min], [lat_max, lon_max]].
|
||||
"""
|
||||
bounds = self._get_self_bounds()
|
||||
|
||||
for child in self._children.values():
|
||||
child_bounds = child.get_bounds()
|
||||
bounds = [
|
||||
[
|
||||
none_min(bounds[0][0], child_bounds[0][0]),
|
||||
none_min(bounds[0][1], child_bounds[0][1]),
|
||||
],
|
||||
[
|
||||
none_max(bounds[1][0], child_bounds[1][0]),
|
||||
none_max(bounds[1][1], child_bounds[1][1]),
|
||||
],
|
||||
]
|
||||
return bounds
|
||||
|
||||
def add_children(
|
||||
self,
|
||||
child: "Element",
|
||||
name: Optional[str] = None,
|
||||
index: Optional[int] = None,
|
||||
) -> "Element":
|
||||
"""Add a child."""
|
||||
warnings.warn(
|
||||
"Method `add_children` is deprecated. Please use `add_child` instead.",
|
||||
FutureWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.add_child(child, name=name, index=index)
|
||||
|
||||
def add_child(
|
||||
self,
|
||||
child: "Element",
|
||||
name: Optional[str] = None,
|
||||
index: Optional[int] = None,
|
||||
) -> "Element":
|
||||
"""Add a child."""
|
||||
if name is None:
|
||||
name = child.get_name()
|
||||
if index is None:
|
||||
self._children[name] = child
|
||||
else:
|
||||
items = [item for item in self._children.items() if item[0] != name]
|
||||
items.insert(int(index), (name, child))
|
||||
self._children = OrderedDict(items)
|
||||
child._parent = self
|
||||
return self
|
||||
|
||||
def add_to(
|
||||
self,
|
||||
parent: "Element",
|
||||
name: Optional[str] = None,
|
||||
index: Optional[int] = None,
|
||||
) -> "Element":
|
||||
"""Add element to a parent."""
|
||||
parent.add_child(self, name=name, index=index)
|
||||
return self
|
||||
|
||||
def to_dict(
|
||||
self,
|
||||
depth: int = -1,
|
||||
ordered: bool = True,
|
||||
**kwargs,
|
||||
) -> Union[dict, OrderedDict]:
|
||||
"""Returns a dict representation of the object."""
|
||||
dict_fun: Type[Union[dict, OrderedDict]]
|
||||
if ordered:
|
||||
dict_fun = OrderedDict
|
||||
else:
|
||||
dict_fun = dict
|
||||
out = dict_fun()
|
||||
out["name"] = self._name
|
||||
out["id"] = self._id
|
||||
if depth != 0:
|
||||
out["children"] = dict_fun(
|
||||
[
|
||||
(name, child.to_dict(depth=depth - 1))
|
||||
for name, child in self._children.items()
|
||||
],
|
||||
)
|
||||
return out
|
||||
|
||||
def to_json(self, depth: int = -1, **kwargs) -> str:
|
||||
"""Returns a JSON representation of the object."""
|
||||
return json.dumps(self.to_dict(depth=depth, ordered=True), **kwargs)
|
||||
|
||||
def get_root(self) -> "Element":
|
||||
"""Returns the root of the elements tree."""
|
||||
if self._parent is None:
|
||||
return self
|
||||
else:
|
||||
return self._parent.get_root()
|
||||
|
||||
def render(self, **kwargs) -> str:
|
||||
"""Renders the HTML representation of the element."""
|
||||
return self._template.render(this=self, kwargs=kwargs)
|
||||
|
||||
def save(
|
||||
self,
|
||||
outfile: Union[str, bytes, Path, BinaryIO],
|
||||
close_file: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
"""Saves an Element into a file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
outfile : str or file object
|
||||
The file (or filename) where you want to output the html.
|
||||
close_file : bool, default True
|
||||
Whether the file has to be closed after write.
|
||||
"""
|
||||
fid: BinaryIO
|
||||
if isinstance(outfile, (str, bytes, Path)):
|
||||
fid = open(outfile, "wb")
|
||||
else:
|
||||
fid = outfile
|
||||
|
||||
root = self.get_root()
|
||||
html = root.render(**kwargs)
|
||||
fid.write(html.encode("utf8"))
|
||||
if close_file:
|
||||
fid.close()
|
||||
|
||||
|
||||
class Link(Element):
|
||||
"""An abstract class for embedding a link in the HTML."""
|
||||
|
||||
def __init__(self, url: str, download: bool = False):
|
||||
super().__init__()
|
||||
self.url = url
|
||||
self.code: Optional[bytes] = None
|
||||
if download:
|
||||
self.get_code()
|
||||
|
||||
def get_code(self) -> bytes:
|
||||
"""Opens the link and returns the response's content."""
|
||||
if self.code is None:
|
||||
self.code = urlopen(self.url).read()
|
||||
return self.code
|
||||
|
||||
def to_dict(
|
||||
self,
|
||||
depth: int = -1,
|
||||
ordered: bool = True,
|
||||
**kwargs,
|
||||
) -> Union[dict, OrderedDict]:
|
||||
"""Returns a dict representation of the object."""
|
||||
out = super().to_dict(depth=depth, ordered=ordered, **kwargs)
|
||||
out["url"] = self.url
|
||||
return out
|
||||
|
||||
|
||||
class JavascriptLink(Link):
|
||||
"""Create a JavascriptLink object based on a url.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
url : str
|
||||
The url to be linked
|
||||
download : bool, default False
|
||||
Whether the target document shall be loaded right now.
|
||||
|
||||
"""
|
||||
|
||||
_template = Template(
|
||||
'{% if kwargs.get("embedded",False) %}'
|
||||
"<script>{{this.get_code()}}</script>"
|
||||
"{% else %}"
|
||||
'<script src="{{this.url}}"></script>'
|
||||
"{% endif %}",
|
||||
)
|
||||
|
||||
def __init__(self, url: str, download: bool = False):
|
||||
super().__init__(url=url, download=download)
|
||||
self._name = "JavascriptLink"
|
||||
|
||||
|
||||
class CssLink(Link):
|
||||
"""Create a CssLink object based on a url.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
url : str
|
||||
The url to be linked
|
||||
download : bool, default False
|
||||
Whether the target document shall be loaded right now.
|
||||
|
||||
"""
|
||||
|
||||
_template = Template(
|
||||
'{% if kwargs.get("embedded",False) %}'
|
||||
"<style>{{this.get_code()}}</style>"
|
||||
"{% else %}"
|
||||
'<link rel="stylesheet" href="{{this.url}}"/>'
|
||||
"{% endif %}",
|
||||
)
|
||||
|
||||
def __init__(self, url: str, download: bool = False):
|
||||
super().__init__(url=url, download=download)
|
||||
self._name = "CssLink"
|
||||
|
||||
|
||||
class Figure(Element):
|
||||
"""Create a Figure object, to plot things into it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : str, default "100%"
|
||||
The width of the Figure.
|
||||
It may be a percentage or pixel value (like "300px").
|
||||
height : str, default None
|
||||
The height of the Figure.
|
||||
It may be a percentage or a pixel value (like "300px").
|
||||
ratio : str, default "60%"
|
||||
A percentage defining the aspect ratio of the Figure.
|
||||
It will be ignored if height is not None.
|
||||
title : str, default None
|
||||
Figure title.
|
||||
figsize : tuple of two int, default None
|
||||
If you're a matplotlib addict, you can overwrite width and
|
||||
height. Values will be converted into pixels in using 60 dpi.
|
||||
For example figsize=(10, 5) will result in
|
||||
width="600px", height="300px".
|
||||
"""
|
||||
|
||||
_template = Template(
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"{% if this.title %}<title>{{this.title}}</title>{% endif %}"
|
||||
" {{this.header.render(**kwargs)}}\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
" {{this.html.render(**kwargs)}}\n"
|
||||
"</body>\n"
|
||||
"<script>\n"
|
||||
" {{this.script.render(**kwargs)}}\n"
|
||||
"</script>\n"
|
||||
"</html>\n",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width: str = "100%",
|
||||
height: Optional[str] = None,
|
||||
ratio: str = "60%",
|
||||
title: Optional[str] = None,
|
||||
figsize: Optional[Tuple[int, int]] = None,
|
||||
):
|
||||
super().__init__()
|
||||
self._name = "Figure"
|
||||
self.header = Element()
|
||||
self.html = Element()
|
||||
self.script = Element()
|
||||
|
||||
self.header._parent = self
|
||||
self.html._parent = self
|
||||
self.script._parent = self
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.ratio = ratio
|
||||
self.title = title
|
||||
if figsize is not None:
|
||||
self.width = str(60 * figsize[0]) + "px"
|
||||
self.height = str(60 * figsize[1]) + "px"
|
||||
|
||||
# Create the meta tag.
|
||||
self.header.add_child(
|
||||
Element(
|
||||
'<meta http-equiv="content-type" content="text/html; charset=UTF-8" />',
|
||||
), # noqa
|
||||
name="meta_http",
|
||||
)
|
||||
|
||||
def to_dict(
|
||||
self,
|
||||
depth: int = -1,
|
||||
ordered: bool = True,
|
||||
**kwargs,
|
||||
) -> Union[dict, OrderedDict]:
|
||||
"""Returns a dict representation of the object."""
|
||||
out = super().to_dict(depth=depth, **kwargs)
|
||||
out["header"] = self.header.to_dict(depth=depth - 1, **kwargs)
|
||||
out["html"] = self.html.to_dict(depth=depth - 1, **kwargs)
|
||||
out["script"] = self.script.to_dict(depth=depth - 1, **kwargs)
|
||||
return out
|
||||
|
||||
def get_root(self) -> "Figure":
|
||||
"""Returns the root of the elements tree."""
|
||||
return self
|
||||
|
||||
def render(self, **kwargs) -> str:
|
||||
"""Renders the HTML representation of the element."""
|
||||
for name, child in self._children.items():
|
||||
child.render(**kwargs)
|
||||
return self._template.render(this=self, kwargs=kwargs)
|
||||
|
||||
def _repr_html_(self, **kwargs) -> str:
|
||||
"""Displays the Figure in a Jupyter notebook."""
|
||||
html = escape(self.render(**kwargs))
|
||||
if self.height is None:
|
||||
iframe = (
|
||||
'<div style="width:{width};">'
|
||||
'<div style="position:relative;width:100%;height:0;padding-bottom:{ratio};">' # noqa
|
||||
'<span style="color:#565656">Make this Notebook Trusted to load map: File -> Trust Notebook</span>' # noqa
|
||||
'<iframe srcdoc="{html}" style="position:absolute;width:100%;height:100%;left:0;top:0;' # noqa
|
||||
'border:none !important;" '
|
||||
"allowfullscreen webkitallowfullscreen mozallowfullscreen>"
|
||||
"</iframe>"
|
||||
"</div></div>"
|
||||
).format(html=html, width=self.width, ratio=self.ratio)
|
||||
else:
|
||||
iframe = (
|
||||
'<iframe srcdoc="{html}" width="{width}" height="{height}"'
|
||||
'style="border:none !important;" '
|
||||
'"allowfullscreen" "webkitallowfullscreen" "mozallowfullscreen">'
|
||||
"</iframe>"
|
||||
).format(html=html, width=self.width, height=self.height)
|
||||
return iframe
|
||||
|
||||
def add_subplot(self, x: int, y: int, n: int, margin: float = 0.05) -> "Div":
|
||||
"""Creates a div child subplot in a matplotlib.figure.add_subplot style.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : int
|
||||
The number of rows in the grid.
|
||||
y : int
|
||||
The number of columns in the grid.
|
||||
n : int
|
||||
The cell number in the grid, counted from 1 to x*y.
|
||||
margin : float, default 0.05
|
||||
Factor to add to the left, top, width and height parameters.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> fig.add_subplot(3, 2, 5)
|
||||
# Create a div in the 5th cell of a 3rows x 2columns
|
||||
grid(bottom-left corner).
|
||||
"""
|
||||
width = 1.0 / y
|
||||
height = 1.0 / x
|
||||
left = ((n - 1) % y) * width
|
||||
top = ((n - 1) // y) * height
|
||||
|
||||
left = left + width * margin
|
||||
top = top + height * margin
|
||||
width = width * (1 - 2.0 * margin)
|
||||
height = height * (1 - 2.0 * margin)
|
||||
|
||||
div = Div(
|
||||
position="absolute",
|
||||
width=f"{100.0 * width}%",
|
||||
height=f"{100.0 * height}%",
|
||||
left=f"{100.0 * left}%",
|
||||
top=f"{100.0 * top}%",
|
||||
)
|
||||
self.add_child(div)
|
||||
return div
|
||||
|
||||
|
||||
class Html(Element):
|
||||
"""Create an HTML div object for embedding data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : str
|
||||
The HTML data to be embedded.
|
||||
script : bool
|
||||
If True, data will be embedded without escaping
|
||||
(suitable for embedding html-ready code)
|
||||
width : int or str, default '100%'
|
||||
The width of the output div element.
|
||||
Ex: 120 , '80%'
|
||||
height : int or str, default '100%'
|
||||
The height of the output div element.
|
||||
Ex: 120 , '80%'
|
||||
"""
|
||||
|
||||
_template = Template(
|
||||
'<div id="{{this.get_name()}}" '
|
||||
'style="width: {{this.width[0]}}{{this.width[1]}}; height: {{this.height[0]}}{{this.height[1]}};">' # noqa
|
||||
"{% if this.script %}{{this.data}}{% else %}{{this.data|e}}{% endif %}</div>",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: str,
|
||||
script: bool = False,
|
||||
width: TypeParseSize = "100%",
|
||||
height: TypeParseSize = "100%",
|
||||
):
|
||||
super().__init__()
|
||||
self._name = "Html"
|
||||
self.script = script
|
||||
self.data = data
|
||||
|
||||
self.width = _parse_size(width)
|
||||
self.height = _parse_size(height)
|
||||
|
||||
|
||||
class Div(Figure):
|
||||
"""Create a Div to be embedded in a Figure.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width: int or str, default '100%'
|
||||
The width of the div in pixels (int) or percentage (str).
|
||||
height: int or str, default '100%'
|
||||
The height of the div in pixels (int) or percentage (str).
|
||||
left: int or str, default '0%'
|
||||
The left-position of the div in pixels (int) or percentage (str).
|
||||
top: int or str, default '0%'
|
||||
The top-position of the div in pixels (int) or percentage (str).
|
||||
position: str, default 'relative'
|
||||
The position policy of the div.
|
||||
Usual values are 'relative', 'absolute', 'fixed', 'static'.
|
||||
"""
|
||||
|
||||
_template = Template(
|
||||
"{% macro header(this, kwargs) %}"
|
||||
"<style> #{{this.get_name()}} {\n"
|
||||
" position : {{this.position}};\n"
|
||||
" width : {{this.width[0]}}{{this.width[1]}};\n"
|
||||
" height: {{this.height[0]}}{{this.height[1]}};\n"
|
||||
" left: {{this.left[0]}}{{this.left[1]}};\n"
|
||||
" top: {{this.top[0]}}{{this.top[1]}};\n"
|
||||
" </style>"
|
||||
"{% endmacro %}"
|
||||
"{% macro html(this, kwargs) %}"
|
||||
'<div id="{{this.get_name()}}">{{this.html.render(**kwargs)}}</div>'
|
||||
"{% endmacro %}",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width: TypeParseSize = "100%",
|
||||
height: TypeParseSize = "100%",
|
||||
left: TypeParseSize = "0%",
|
||||
top: TypeParseSize = "0%",
|
||||
position: str = "relative",
|
||||
):
|
||||
super(Figure, self).__init__()
|
||||
self._name = "Div"
|
||||
|
||||
# Size Parameters.
|
||||
self.width = _parse_size(width) # type: ignore
|
||||
self.height = _parse_size(height) # type: ignore
|
||||
self.left = _parse_size(left)
|
||||
self.top = _parse_size(top)
|
||||
self.position = position
|
||||
|
||||
self.header = Element()
|
||||
self.html = Element(
|
||||
"{% for name, element in this._children.items() %}"
|
||||
"{{element.render(**kwargs)}}"
|
||||
"{% endfor %}",
|
||||
)
|
||||
self.script = Element()
|
||||
|
||||
self.header._parent = self
|
||||
self.html._parent = self
|
||||
self.script._parent = self
|
||||
|
||||
def get_root(self) -> "Div":
|
||||
"""Returns the root of the elements tree."""
|
||||
return self
|
||||
|
||||
def render(self, **kwargs):
|
||||
"""Renders the HTML representation of the element."""
|
||||
figure = self._parent
|
||||
assert isinstance(figure, Figure), (
|
||||
"You cannot render this Element " "if it is not in a Figure."
|
||||
)
|
||||
|
||||
for name, element in self._children.items():
|
||||
element.render(**kwargs)
|
||||
|
||||
for name, element in self.header._children.items():
|
||||
figure.header.add_child(element, name=name)
|
||||
|
||||
for name, element in self.script._children.items():
|
||||
figure.script.add_child(element, name=name)
|
||||
|
||||
header = self._template.module.__dict__.get("header", None)
|
||||
if header is not None:
|
||||
figure.header.add_child(Element(header(self, kwargs)), name=self.get_name())
|
||||
|
||||
html = self._template.module.__dict__.get("html", None)
|
||||
if html is not None:
|
||||
figure.html.add_child(Element(html(self, kwargs)), name=self.get_name())
|
||||
|
||||
script = self._template.module.__dict__.get("script", None)
|
||||
if script is not None:
|
||||
figure.script.add_child(Element(script(self, kwargs)), name=self.get_name())
|
||||
|
||||
def _repr_html_(self, **kwargs) -> str:
|
||||
"""Displays the Div in a Jupyter notebook."""
|
||||
if self._parent is None:
|
||||
self.add_to(Figure())
|
||||
out = self._parent._repr_html_(**kwargs) # type: ignore
|
||||
self._parent = None
|
||||
else:
|
||||
out = self._parent._repr_html_(**kwargs) # type: ignore
|
||||
return out
|
||||
|
||||
|
||||
class IFrame(Element):
|
||||
"""Create a Figure object, to plot things into it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
html : str, default None
|
||||
Eventual HTML code that you want to put in the frame.
|
||||
width : str, default "100%"
|
||||
The width of the Figure.
|
||||
It may be a percentage or pixel value (like "300px").
|
||||
height : str, default None
|
||||
The height of the Figure.
|
||||
It may be a percentage or a pixel value (like "300px").
|
||||
ratio : str, default "60%"
|
||||
A percentage defining the aspect ratio of the Figure.
|
||||
It will be ignored if height is not None.
|
||||
figsize : tuple of two int, default None
|
||||
If you're a matplotlib addict, you can overwrite width and
|
||||
height. Values will be converted into pixels in using 60 dpi.
|
||||
For example figsize=(10, 5) will result in
|
||||
width="600px", height="300px".
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
html: Optional[Union[str, Element]] = None,
|
||||
width: str = "100%",
|
||||
height: Optional[str] = None,
|
||||
ratio: str = "60%",
|
||||
figsize: Optional[Tuple[int, int]] = None,
|
||||
):
|
||||
super().__init__()
|
||||
self._name = "IFrame"
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.ratio = ratio
|
||||
if figsize is not None:
|
||||
self.width = str(60 * figsize[0]) + "px"
|
||||
self.height = str(60 * figsize[1]) + "px"
|
||||
|
||||
if isinstance(html, str):
|
||||
self.add_child(Element(html))
|
||||
elif html is not None:
|
||||
self.add_child(html)
|
||||
|
||||
def render(self, **kwargs) -> str:
|
||||
"""Renders the HTML representation of the element."""
|
||||
html = super().render(**kwargs)
|
||||
html = "data:text/html;charset=utf-8;base64," + base64.b64encode(
|
||||
html.encode("utf8"),
|
||||
).decode("utf8")
|
||||
|
||||
if self.height is None:
|
||||
iframe = (
|
||||
'<div style="width:{width};">'
|
||||
'<div style="position:relative;width:100%;height:0;padding-bottom:{ratio};">' # noqa
|
||||
'<iframe src="{html}" style="position:absolute;width:100%;height:100%;left:0;top:0;' # noqa
|
||||
'border:none !important;">'
|
||||
"</iframe>"
|
||||
"</div></div>"
|
||||
).format(html=html, width=self.width, ratio=self.ratio)
|
||||
else:
|
||||
iframe = (
|
||||
'<iframe src="{html}" width="{width}" style="border:none !important;" '
|
||||
'height="{height}"></iframe>'
|
||||
).format(html=html, width=self.width, height=self.height)
|
||||
return iframe
|
||||
|
||||
|
||||
class MacroElement(Element):
|
||||
"""This is a parent class for Elements defined by a macro template.
|
||||
To compute your own element, all you have to do is:
|
||||
|
||||
* To inherit from this class
|
||||
* Overwrite the '_name' attribute
|
||||
* Overwrite the '_template' attribute with something of the form::
|
||||
|
||||
{% macro header(this, kwargs) %}
|
||||
...
|
||||
{% endmacro %}
|
||||
|
||||
{% macro html(this, kwargs) %}
|
||||
...
|
||||
{% endmacro %}
|
||||
|
||||
{% macro script(this, kwargs) %}
|
||||
...
|
||||
{% endmacro %}
|
||||
|
||||
"""
|
||||
|
||||
_template = Template("")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._name = "MacroElement"
|
||||
|
||||
def render(self, **kwargs):
|
||||
"""Renders the HTML representation of the element."""
|
||||
figure = self.get_root()
|
||||
assert isinstance(figure, Figure), (
|
||||
"You cannot render this Element " "if it is not in a Figure."
|
||||
)
|
||||
|
||||
header = self._template.module.__dict__.get("header", None)
|
||||
if header is not None:
|
||||
figure.header.add_child(Element(header(self, kwargs)), name=self.get_name())
|
||||
|
||||
html = self._template.module.__dict__.get("html", None)
|
||||
if html is not None:
|
||||
figure.html.add_child(Element(html(self, kwargs)), name=self.get_name())
|
||||
|
||||
script = self._template.module.__dict__.get("script", None)
|
||||
if script is not None:
|
||||
figure.script.add_child(Element(script(self, kwargs)), name=self.get_name())
|
||||
|
||||
for name, element in self._children.items():
|
||||
element.render(**kwargs)
|
||||
0
.venv/lib/python3.12/site-packages/branca/py.typed
Normal file
0
.venv/lib/python3.12/site-packages/branca/py.typed
Normal file
@@ -0,0 +1 @@
|
||||
{"codes": ["viridis", "plasma", "inferno", "magma", "Spectral", "RdYlGn", "PuBu", "Accent", "OrRd", "Set1", "Set2", "Set3", "BuPu", "Dark2", "RdBu", "Oranges", "BuGn", "PiYG", "YlOrBr", "YlGn", "Pastel2", "RdPu", "Greens", "PRGn", "YlGnBu", "RdYlBu", "Paired", "BrBG", "Purples", "Reds", "Pastel1", "GnBu", "Greys", "RdGy", "YlOrRd", "PuOr", "PuRd", "Blues", "PuBuGn"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"Spectral": "Diverging", "RdYlGn": "Diverging", "Set2": "Qualitative", "Accent": "Qualitative", "OrRd": "Sequential", "Set1": "Qualitative", "PuBu": "Sequential", "Set3": "Qualitative", "BuPu": "Sequential", "Dark2": "Qualitative", "RdBu": "Diverging", "BuGn": "Sequential", "PiYG": "Diverging", "YlOrBr": "Sequential", "YlGn": "Sequential", "RdPu": "Sequential", "PRGn": "Diverging", "YlGnBu": "Sequential", "RdYlBu": "Diverging", "Paired": "Qualitative", "Pastel2": "Qualitative", "Pastel1": "Qualitative", "GnBu": "Sequential", "RdGy": "Diverging", "YlOrRd": "Sequential", "PuOr": "Diverging", "PuRd": "Sequential", "BrBG": "Diverging", "PuBuGn": "Sequential", "Greens": "Sequential", "viridis": "Sequential", "plasma": "Sequential", "inferno": "Sequential", "magma": "Sequential", "Oranges": "Sequential", "Blues": "Sequential", "Greys": "Sequential", "Reds": "Sequential", "Purples": "Sequential"}
|
||||
@@ -0,0 +1,57 @@
|
||||
{% macro script(this, kwargs) %}
|
||||
var {{this.get_name()}} = {};
|
||||
|
||||
{%if this.color_range %}
|
||||
{{this.get_name()}}.color = d3.scale.threshold()
|
||||
.domain({{this.color_domain}})
|
||||
.range({{this.color_range}});
|
||||
{%else%}
|
||||
{{this.get_name()}}.color = d3.scale.threshold()
|
||||
.domain([{{ this.color_domain[0] }}, {{ this.color_domain[-1] }}])
|
||||
.range(['{{ this.fill_color }}', '{{ this.fill_color }}']);
|
||||
{%endif%}
|
||||
|
||||
{{this.get_name()}}.x = d3.scale.linear()
|
||||
.domain([{{ this.color_domain[0] }}, {{ this.color_domain[-1] }}])
|
||||
.range([0, {{ this.width }} - 50]);
|
||||
|
||||
{{this.get_name()}}.legend = L.control({position: 'topright'});
|
||||
{{this.get_name()}}.legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div};
|
||||
{{this.get_name()}}.legend.addTo({{this._parent.get_name()}});
|
||||
|
||||
{{this.get_name()}}.xAxis = d3.svg.axis()
|
||||
.scale({{this.get_name()}}.x)
|
||||
.orient("top")
|
||||
.tickSize(1)
|
||||
.tickValues({{ this.tick_labels }});
|
||||
|
||||
{{this.get_name()}}.svg = d3.select(".legend.leaflet-control").append("svg")
|
||||
.attr("id", 'legend')
|
||||
.attr("width", {{ this.width }})
|
||||
.attr("height", {{ this.height }});
|
||||
|
||||
{{this.get_name()}}.g = {{this.get_name()}}.svg.append("g")
|
||||
.attr("class", "key")
|
||||
.attr("fill", {{ this.text_color | tojson }})
|
||||
.attr("transform", "translate(25,16)");
|
||||
|
||||
{{this.get_name()}}.g.selectAll("rect")
|
||||
.data({{this.get_name()}}.color.range().map(function(d, i) {
|
||||
return {
|
||||
x0: i ? {{this.get_name()}}.x({{this.get_name()}}.color.domain()[i - 1]) : {{this.get_name()}}.x.range()[0],
|
||||
x1: i < {{this.get_name()}}.color.domain().length ? {{this.get_name()}}.x({{this.get_name()}}.color.domain()[i]) : {{this.get_name()}}.x.range()[1],
|
||||
z: d
|
||||
};
|
||||
}))
|
||||
.enter().append("rect")
|
||||
.attr("height", {{ this.height }} - 30)
|
||||
.attr("x", function(d) { return d.x0; })
|
||||
.attr("width", function(d) { return d.x1 - d.x0; })
|
||||
.style("fill", function(d) { return d.z; });
|
||||
|
||||
{{this.get_name()}}.g.call({{this.get_name()}}.xAxis).append("text")
|
||||
.attr("class", "caption")
|
||||
.attr("y", 21)
|
||||
.attr("fill", {{ this.text_color | tojson }})
|
||||
.text({{ this.caption|tojson }});
|
||||
{% endmacro %}
|
||||
460
.venv/lib/python3.12/site-packages/branca/utilities.py
Normal file
460
.venv/lib/python3.12/site-packages/branca/utilities.py
Normal file
@@ -0,0 +1,460 @@
|
||||
"""
|
||||
Utilities
|
||||
-------
|
||||
|
||||
Utility module for Folium helper functions.
|
||||
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import typing
|
||||
import zlib
|
||||
from typing import Any, Callable, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
from jinja2 import Environment, PackageLoader
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
np = None # type: ignore
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from branca.colormap import ColorMap
|
||||
|
||||
|
||||
rootpath: str = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
TypeParseSize = Union[int, float, str, Tuple[float, str]]
|
||||
|
||||
|
||||
def get_templates() -> Environment:
|
||||
"""Get Jinja templates."""
|
||||
return Environment(loader=PackageLoader("branca", "templates"))
|
||||
|
||||
|
||||
def legend_scaler(
|
||||
legend_values: Sequence[float],
|
||||
max_labels: int = 10,
|
||||
) -> List[Union[float, str]]:
|
||||
"""
|
||||
Downsamples the number of legend values so that there isn't a collision
|
||||
of text on the legend colorbar (within reason). The colorbar seems to
|
||||
support ~10 entries as a maximum.
|
||||
|
||||
"""
|
||||
legend_ticks: List[Union[float, str]]
|
||||
if len(legend_values) < max_labels:
|
||||
legend_ticks = list(legend_values)
|
||||
else:
|
||||
spacer = int(math.ceil(len(legend_values) / max_labels))
|
||||
legend_ticks = []
|
||||
for i in legend_values[::spacer]:
|
||||
legend_ticks += [i]
|
||||
legend_ticks += [""] * (spacer - 1)
|
||||
return legend_ticks
|
||||
|
||||
|
||||
def linear_gradient(hexList: List[str], nColors: int) -> List[str]:
|
||||
"""
|
||||
Given a list of hexcode values, will return a list of length
|
||||
nColors where the colors are linearly interpolated between the
|
||||
(r, g, b) tuples that are given.
|
||||
"""
|
||||
|
||||
def _scale(start, finish, length, i):
|
||||
"""
|
||||
Return the value correct value of a number that is in between start
|
||||
and finish, for use in a loop of length *length*.
|
||||
|
||||
"""
|
||||
base = 16
|
||||
|
||||
fraction = float(i) / (length - 1)
|
||||
raynge = int(finish, base) - int(start, base)
|
||||
thex = hex(int(int(start, base) + fraction * raynge)).split("x")[-1]
|
||||
if len(thex) != 2:
|
||||
thex = "0" + thex
|
||||
return thex
|
||||
|
||||
allColors: List[str] = []
|
||||
# Separate (R, G, B) pairs.
|
||||
for start, end in zip(hexList[:-1], hexList[1:]):
|
||||
# Linearly interpolate between pair of hex ###### values and
|
||||
# add to list.
|
||||
nInterpolate = 765
|
||||
for index in range(nInterpolate):
|
||||
r = _scale(start[1:3], end[1:3], nInterpolate, index)
|
||||
g = _scale(start[3:5], end[3:5], nInterpolate, index)
|
||||
b = _scale(start[5:7], end[5:7], nInterpolate, index)
|
||||
allColors.append("".join(["#", r, g, b]))
|
||||
|
||||
# Pick only nColors colors from the total list.
|
||||
result: List[str] = []
|
||||
for counter in range(nColors):
|
||||
fraction = float(counter) / (nColors - 1)
|
||||
index = int(fraction * (len(allColors) - 1))
|
||||
result.append(allColors[index])
|
||||
return result
|
||||
|
||||
|
||||
def color_brewer(color_code: str, n: int = 6) -> List[str]:
|
||||
"""
|
||||
Generate a colorbrewer color scheme of length 'len', type 'scheme.
|
||||
Live examples can be seen at http://colorbrewer2.org/
|
||||
|
||||
"""
|
||||
maximum_n = 253
|
||||
minimum_n = 3
|
||||
|
||||
if not isinstance(n, int):
|
||||
raise TypeError("n has to be an int, not a %s" % type(n))
|
||||
|
||||
# Raise an error if the n requested is greater than the maximum.
|
||||
if n > maximum_n:
|
||||
raise ValueError(
|
||||
"The maximum number of colors in a"
|
||||
" ColorBrewer sequential color series is 253",
|
||||
)
|
||||
if n < minimum_n:
|
||||
raise ValueError(
|
||||
"The minimum number of colors in a"
|
||||
" ColorBrewer sequential color series is 3",
|
||||
)
|
||||
|
||||
if not isinstance(color_code, str):
|
||||
raise ValueError(f"color should be a string, not a {type(color_code)}.")
|
||||
if color_code[-2:] == "_r":
|
||||
base_code = color_code[:-2]
|
||||
core_color_code = base_code + "_" + str(n).zfill(2)
|
||||
color_reverse = True
|
||||
else:
|
||||
base_code = color_code
|
||||
core_color_code = base_code + "_" + str(n).zfill(2)
|
||||
color_reverse = False
|
||||
|
||||
with open(os.path.join(rootpath, "_schemes.json")) as f:
|
||||
schemes = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(rootpath, "scheme_info.json")) as f:
|
||||
scheme_info = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(rootpath, "scheme_base_codes.json")) as f:
|
||||
core_schemes = json.loads(f.read())["codes"]
|
||||
|
||||
if base_code not in core_schemes:
|
||||
raise ValueError(base_code + " is not a valid ColorBrewer code")
|
||||
|
||||
explicit_scheme = True
|
||||
if schemes.get(core_color_code) is None:
|
||||
explicit_scheme = False
|
||||
|
||||
# Only if n is greater than the scheme length do we interpolate values.
|
||||
if not explicit_scheme:
|
||||
# Check to make sure that it is not a qualitative scheme.
|
||||
if scheme_info[base_code] == "Qualitative":
|
||||
matching_quals = []
|
||||
for key in schemes:
|
||||
if base_code + "_" in key:
|
||||
matching_quals.append(int(key.split("_")[1]))
|
||||
|
||||
raise ValueError(
|
||||
"Expanded color support is not available"
|
||||
" for Qualitative schemes; restrict the"
|
||||
" number of colors for the "
|
||||
+ base_code
|
||||
+ " code to between "
|
||||
+ str(min(matching_quals))
|
||||
+ " and "
|
||||
+ str(max(matching_quals)),
|
||||
)
|
||||
else:
|
||||
longest_scheme_name = base_code
|
||||
longest_scheme_n = 0
|
||||
for sn_name in schemes.keys():
|
||||
if "_" not in sn_name:
|
||||
continue
|
||||
if sn_name.split("_")[0] != base_code:
|
||||
continue
|
||||
if int(sn_name.split("_")[1]) > longest_scheme_n:
|
||||
longest_scheme_name = sn_name
|
||||
longest_scheme_n = int(sn_name.split("_")[1])
|
||||
|
||||
if not color_reverse:
|
||||
color_scheme = linear_gradient(schemes.get(longest_scheme_name), n)
|
||||
else:
|
||||
color_scheme = linear_gradient(
|
||||
schemes.get(longest_scheme_name)[::-1],
|
||||
n,
|
||||
)
|
||||
else:
|
||||
if not color_reverse:
|
||||
color_scheme = schemes.get(core_color_code, None)
|
||||
else:
|
||||
color_scheme = schemes.get(core_color_code, None)[::-1]
|
||||
return color_scheme
|
||||
|
||||
|
||||
def image_to_url(
|
||||
image: Any,
|
||||
colormap: Union["ColorMap", Callable, None] = None,
|
||||
origin: str = "upper",
|
||||
) -> str:
|
||||
"""Infers the type of an image argument and transforms it into a URL.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image: string, file or array-like object
|
||||
* If string, it will be written directly in the output file.
|
||||
* If file, it's content will be converted as embedded in the
|
||||
output file.
|
||||
* If array-like, it will be converted to PNG base64 string and
|
||||
embedded in the output.
|
||||
origin : ['upper' | 'lower'], optional, default 'upper'
|
||||
Place the [0, 0] index of the array in the upper left or
|
||||
lower left corner of the axes.
|
||||
colormap : ColorMap or callable, used only for `mono` image.
|
||||
Function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)]
|
||||
for transforming a mono image into RGB.
|
||||
It must output iterables of length 3 or 4, with values between
|
||||
0. and 1. Hint : you can use colormaps from `matplotlib.cm`.
|
||||
"""
|
||||
if hasattr(image, "read"):
|
||||
# We got an image file.
|
||||
if hasattr(image, "name"):
|
||||
# We try to get the image format from the file name.
|
||||
fileformat = image.name.lower().split(".")[-1]
|
||||
else:
|
||||
fileformat = "png"
|
||||
url = "data:image/{};base64,{}".format(
|
||||
fileformat,
|
||||
base64.b64encode(image.read()).decode("utf-8"),
|
||||
)
|
||||
elif (not (isinstance(image, str) or isinstance(image, bytes))) and hasattr(
|
||||
image,
|
||||
"__iter__",
|
||||
):
|
||||
# We got an array-like object.
|
||||
png = write_png(image, origin=origin, colormap=colormap)
|
||||
url = "data:image/png;base64," + base64.b64encode(png).decode("utf-8")
|
||||
else:
|
||||
# We got an URL.
|
||||
url = json.loads(json.dumps(image))
|
||||
|
||||
return url.replace("\n", " ")
|
||||
|
||||
|
||||
def write_png(
|
||||
data: Any,
|
||||
origin: str = "upper",
|
||||
colormap: Union["ColorMap", Callable, None] = None,
|
||||
) -> bytes:
|
||||
"""
|
||||
Transform an array of data into a PNG string.
|
||||
This can be written to disk using binary I/O, or encoded using base64
|
||||
for an inline PNG like this:
|
||||
|
||||
>>> png_str = write_png(array)
|
||||
>>> "data:image/png;base64," + png_str.encode("base64")
|
||||
|
||||
Inspired from
|
||||
http://stackoverflow.com/questions/902761/saving-a-numpy-array-as-an-image
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data: numpy array or equivalent list-like object.
|
||||
Must be NxM (mono), NxMx3 (RGB) or NxMx4 (RGBA)
|
||||
origin : ['upper' | 'lower'], optional, default 'upper'
|
||||
Place the [0,0] index of the array in the upper left or lower left
|
||||
corner of the axes.
|
||||
colormap : ColorMap subclass or callable, optional
|
||||
Only needed to transform mono images into RGB. You have three options:
|
||||
- use a subclass of `ColorMap` like `LinearColorMap`
|
||||
- use a colormap from `matplotlib.cm`
|
||||
- use a custom function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)].
|
||||
It must output iterables of length 3 or 4 with values between 0 and 1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
PNG formatted byte string
|
||||
"""
|
||||
from branca.colormap import ColorMap
|
||||
|
||||
if np is None:
|
||||
raise ImportError("The NumPy package is required" " for this functionality")
|
||||
|
||||
if isinstance(colormap, ColorMap):
|
||||
colormap_callable = colormap.rgba_floats_tuple
|
||||
elif callable(colormap):
|
||||
colormap_callable = colormap
|
||||
else:
|
||||
colormap_callable = lambda x: (x, x, x, 1) # noqa E731
|
||||
|
||||
array = np.atleast_3d(data)
|
||||
height, width, nblayers = array.shape
|
||||
|
||||
if nblayers not in [1, 3, 4]:
|
||||
raise ValueError("Data must be NxM (mono), " "NxMx3 (RGB), or NxMx4 (RGBA)")
|
||||
assert array.shape == (height, width, nblayers)
|
||||
|
||||
if nblayers == 1:
|
||||
array = np.array(list(map(colormap_callable, array.ravel())))
|
||||
nblayers = array.shape[1]
|
||||
if nblayers not in [3, 4]:
|
||||
raise ValueError(
|
||||
"colormap must provide colors of" "length 3 (RGB) or 4 (RGBA)",
|
||||
)
|
||||
array = array.reshape((height, width, nblayers))
|
||||
assert array.shape == (height, width, nblayers)
|
||||
|
||||
if nblayers == 3:
|
||||
array = np.concatenate((array, np.ones((height, width, 1))), axis=2)
|
||||
nblayers = 4
|
||||
assert array.shape == (height, width, nblayers)
|
||||
assert nblayers == 4
|
||||
|
||||
# Normalize to uint8 if it isn't already.
|
||||
if array.dtype != "uint8":
|
||||
with np.errstate(divide="ignore", invalid="ignore"):
|
||||
array = array * 255.0 / array.max(axis=(0, 1)).reshape((1, 1, 4))
|
||||
array[~np.isfinite(array)] = 0
|
||||
array = array.astype("uint8")
|
||||
|
||||
# Eventually flip the image.
|
||||
if origin == "lower":
|
||||
array = array[::-1, :, :]
|
||||
|
||||
# Transform the array to bytes.
|
||||
raw_data = b"".join([b"\x00" + array[i, :, :].tobytes() for i in range(height)])
|
||||
|
||||
def png_pack(png_tag, data):
|
||||
chunk_head = png_tag + data
|
||||
return (
|
||||
struct.pack("!I", len(data))
|
||||
+ chunk_head
|
||||
+ struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
|
||||
)
|
||||
|
||||
return b"".join(
|
||||
[
|
||||
b"\x89PNG\r\n\x1a\n",
|
||||
png_pack(b"IHDR", struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
|
||||
png_pack(b"IDAT", zlib.compress(raw_data, 9)),
|
||||
png_pack(b"IEND", b""),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _camelify(out: str) -> str:
|
||||
return (
|
||||
(
|
||||
"".join(
|
||||
[
|
||||
(
|
||||
"_" + x.lower()
|
||||
if i < len(out) - 1 and x.isupper() and out[i + 1].islower()
|
||||
else (
|
||||
x.lower() + "_"
|
||||
if i < len(out) - 1 and x.islower() and out[i + 1].isupper()
|
||||
else x.lower()
|
||||
)
|
||||
)
|
||||
for i, x in enumerate(list(out))
|
||||
],
|
||||
)
|
||||
)
|
||||
.lstrip("_")
|
||||
.replace("__", "_")
|
||||
)
|
||||
|
||||
|
||||
def _parse_size(value: TypeParseSize) -> Tuple[float, str]:
|
||||
if isinstance(value, (int, float)):
|
||||
return float(value), "px"
|
||||
elif isinstance(value, str):
|
||||
# match digits or a point, possibly followed by a space,
|
||||
# followed by a unit: either 1 to 5 letters or a percent sign
|
||||
match = re.fullmatch(r"([\d.]+)\s?(\w{1,5}|%)", value.strip())
|
||||
if match:
|
||||
return float(match.group(1)), match.group(2)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Cannot parse {value!r}, it should be a number followed by a unit.",
|
||||
)
|
||||
elif (
|
||||
isinstance(value, tuple)
|
||||
and isinstance(value[0], (int, float))
|
||||
and isinstance(value[1], str)
|
||||
):
|
||||
# value had been already parsed
|
||||
return (float(value[0]), value[1])
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Cannot parse {value!r}, it should be a number or a string containing a number and a unit.",
|
||||
)
|
||||
|
||||
|
||||
def _locations_mirror(x):
|
||||
"""Mirrors the points in a list-of-list-of-...-of-list-of-points.
|
||||
For example:
|
||||
>>> _locations_mirror([[[1, 2], [3, 4]], [5, 6], [7, 8]])
|
||||
[[[2, 1], [4, 3]], [6, 5], [8, 7]]
|
||||
|
||||
"""
|
||||
if hasattr(x, "__iter__"):
|
||||
if hasattr(x[0], "__iter__"):
|
||||
return list(map(_locations_mirror, x))
|
||||
else:
|
||||
return list(x[::-1])
|
||||
else:
|
||||
return x
|
||||
|
||||
|
||||
def _locations_tolist(x):
|
||||
"""Transforms recursively a list of iterables into a list of list."""
|
||||
if hasattr(x, "__iter__"):
|
||||
return list(map(_locations_tolist, x))
|
||||
else:
|
||||
return x
|
||||
|
||||
|
||||
def none_min(x: Optional[float], y: Optional[float]) -> Optional[float]:
|
||||
if x is None:
|
||||
return y
|
||||
elif y is None:
|
||||
return x
|
||||
else:
|
||||
return min(x, y)
|
||||
|
||||
|
||||
def none_max(x: Optional[float], y: Optional[float]) -> Optional[float]:
|
||||
if x is None:
|
||||
return y
|
||||
elif y is None:
|
||||
return x
|
||||
else:
|
||||
return max(x, y)
|
||||
|
||||
|
||||
def iter_points(x: Union[List, Tuple]) -> list:
|
||||
"""Iterates over a list representing a feature, and returns a list of points,
|
||||
whatever the shape of the array (Point, MultiPolyline, etc).
|
||||
"""
|
||||
if isinstance(x, (list, tuple)):
|
||||
if len(x):
|
||||
if isinstance(x[0], (list, tuple)):
|
||||
out = []
|
||||
for y in x:
|
||||
out += iter_points(y)
|
||||
return out
|
||||
else:
|
||||
return [x]
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
raise ValueError(f"List/tuple type expected. Got {x!r}.")
|
||||
Reference in New Issue
Block a user