fastplotpy 0.3.0a1__py3-none-win_amd64.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.
- fastplotpy/README.md +70 -0
- fastplotpy/__init__.py +33 -0
- fastplotpy/assets/dist/assets/scene.worker-CcnexaKr.js +1 -0
- fastplotpy/assets/dist/index.js +15071 -0
- fastplotpy/assets/native/NotoSans-OFL.txt +96 -0
- fastplotpy/assets/native/THIRD_PARTY_NOTICES.md +24 -0
- fastplotpy/assets/native/fastplot_python_abi.dll +0 -0
- fastplotpy/assets/package.json +1 -0
- fastplotpy/axes_validation.py +102 -0
- fastplotpy/camera3d.py +104 -0
- fastplotpy/color_scale.py +88 -0
- fastplotpy/defaults/area-fill.json +5 -0
- fastplotpy/defaults/bar.json +22 -0
- fastplotpy/defaults/camera3d.json +22 -0
- fastplotpy/defaults/cartesian-references.json +10 -0
- fastplotpy/defaults/control.json +3 -0
- fastplotpy/defaults/crosshair.json +19 -0
- fastplotpy/defaults/error-bars.json +7 -0
- fastplotpy/defaults/heatmap.json +70 -0
- fastplotpy/defaults/interaction.json +3 -0
- fastplotpy/defaults/layout.json +32 -0
- fastplotpy/defaults/legend.json +24 -0
- fastplotpy/defaults/line-axes.json +28 -0
- fastplotpy/defaults/line3d.json +13 -0
- fastplotpy/defaults/markers.json +6 -0
- fastplotpy/defaults/mesh3d.json +13 -0
- fastplotpy/defaults/pan.json +7 -0
- fastplotpy/defaults/pie.json +27 -0
- fastplotpy/defaults/pie3d.json +7 -0
- fastplotpy/defaults/point-labels.json +13 -0
- fastplotpy/defaults/pointClick.json +6 -0
- fastplotpy/defaults/pointSelection.json +13 -0
- fastplotpy/defaults/polar.json +71 -0
- fastplotpy/defaults/polarRotate.json +5 -0
- fastplotpy/defaults/scatter3d.json +39 -0
- fastplotpy/defaults/scene3d.json +61 -0
- fastplotpy/defaults/surface.json +35 -0
- fastplotpy/defaults/tooltip.json +18 -0
- fastplotpy/defaults/trace-colors.json +10 -0
- fastplotpy/defaults/trace-options.json +24 -0
- fastplotpy/defaults/wonderplot.json +58 -0
- fastplotpy/defaults/zoom.json +12 -0
- fastplotpy/defaults.py +15 -0
- fastplotpy/error_bars.py +118 -0
- fastplotpy/exports.py +153 -0
- fastplotpy/families2d.py +896 -0
- fastplotpy/heatmap_labels.py +146 -0
- fastplotpy/interactions.py +243 -0
- fastplotpy/interactive.py +1922 -0
- fastplotpy/js_serialize.py +47 -0
- fastplotpy/lighting3d.py +113 -0
- fastplotpy/line3d_lod.py +138 -0
- fastplotpy/lod3d.py +105 -0
- fastplotpy/mesh3d_lod.py +107 -0
- fastplotpy/native.py +163 -0
- fastplotpy/overlays.py +561 -0
- fastplotpy/point_labels.py +86 -0
- fastplotpy/polar.py +445 -0
- fastplotpy/polygon_clip3d.py +148 -0
- fastplotpy/references.py +72 -0
- fastplotpy/rendering.py +65 -0
- fastplotpy/rust_scenes.py +314 -0
- fastplotpy/scene.py +1185 -0
- fastplotpy/scene3d.py +1215 -0
- fastplotpy/scene3d_accessibility.py +183 -0
- fastplotpy/scene3d_collection.py +158 -0
- fastplotpy/subplots.py +115 -0
- fastplotpy/surface_contours.py +122 -0
- fastplotpy/surface_lod.py +109 -0
- fastplotpy/svg3d.py +235 -0
- fastplotpy/trendline.py +236 -0
- fastplotpy/triangle_wireframe.py +51 -0
- fastplotpy/types.py +3027 -0
- fastplotpy-0.3.0a1.dist-info/METADATA +86 -0
- fastplotpy-0.3.0a1.dist-info/RECORD +78 -0
- fastplotpy-0.3.0a1.dist-info/WHEEL +5 -0
- fastplotpy-0.3.0a1.dist-info/licenses/LICENSE +21 -0
- fastplotpy-0.3.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License,
|
|
4
|
+
Version 1.1.
|
|
5
|
+
|
|
6
|
+
This license is copied below, and is also available with a FAQ at:
|
|
7
|
+
http://scripts.sil.org/OFL
|
|
8
|
+
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
11
|
+
-----------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
PREAMBLE
|
|
14
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
15
|
+
development of collaborative font projects, to support the font
|
|
16
|
+
creation efforts of academic and linguistic communities, and to
|
|
17
|
+
provide a free and open framework in which fonts may be shared and
|
|
18
|
+
improved in partnership with others.
|
|
19
|
+
|
|
20
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
21
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
22
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
23
|
+
redistributed and/or sold with any software provided that any reserved
|
|
24
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
25
|
+
however, cannot be released under any other type of license. The
|
|
26
|
+
requirement for fonts to remain under this license does not apply to
|
|
27
|
+
any document created using the fonts or their derivatives.
|
|
28
|
+
|
|
29
|
+
DEFINITIONS
|
|
30
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
31
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
32
|
+
include source files, build scripts and documentation.
|
|
33
|
+
|
|
34
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
35
|
+
copyright statement(s).
|
|
36
|
+
|
|
37
|
+
"Original Version" refers to the collection of Font Software
|
|
38
|
+
components as distributed by the Copyright Holder(s).
|
|
39
|
+
|
|
40
|
+
"Modified Version" refers to any derivative made by adding to,
|
|
41
|
+
deleting, or substituting -- in part or in whole -- any of the
|
|
42
|
+
components of the Original Version, by changing formats or by porting
|
|
43
|
+
the Font Software to a new environment.
|
|
44
|
+
|
|
45
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
46
|
+
writer or other person who contributed to the Font Software.
|
|
47
|
+
|
|
48
|
+
PERMISSION & CONDITIONS
|
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
50
|
+
a copy of the Font Software, to use, study, copy, merge, embed,
|
|
51
|
+
modify, redistribute, and sell modified and unmodified copies of the
|
|
52
|
+
Font Software, subject to the following conditions:
|
|
53
|
+
|
|
54
|
+
1) Neither the Font Software nor any of its individual components, in
|
|
55
|
+
Original or Modified Versions, may be sold by itself.
|
|
56
|
+
|
|
57
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
58
|
+
redistributed and/or sold with any software, provided that each copy
|
|
59
|
+
contains the above copyright notice and this license. These can be
|
|
60
|
+
included either as stand-alone text files, human-readable headers or
|
|
61
|
+
in the appropriate machine-readable metadata fields within text or
|
|
62
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
63
|
+
|
|
64
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
65
|
+
Name(s) unless explicit written permission is granted by the
|
|
66
|
+
corresponding Copyright Holder. This restriction only applies to the
|
|
67
|
+
primary font name as presented to the users.
|
|
68
|
+
|
|
69
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
70
|
+
Software shall not be used to promote, endorse or advertise any
|
|
71
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
72
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
73
|
+
permission.
|
|
74
|
+
|
|
75
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
76
|
+
must be distributed entirely under this license, and must not be
|
|
77
|
+
distributed under any other license. The requirement for fonts to
|
|
78
|
+
remain under this license does not apply to any document created using
|
|
79
|
+
the Font Software.
|
|
80
|
+
|
|
81
|
+
TERMINATION
|
|
82
|
+
This license becomes null and void if any of the above conditions are
|
|
83
|
+
not met.
|
|
84
|
+
|
|
85
|
+
DISCLAIMER
|
|
86
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
87
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
88
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
89
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
90
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
91
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
92
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
93
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
94
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
95
|
+
|
|
96
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
The optional Python-host Rust native library embeds two unmodified font files from the archived
|
|
4
|
+
`notofonts/noto-fonts` repository at commit
|
|
5
|
+
`ffebf8c1ee449e544955a7e813c54f9b73848eac`:
|
|
6
|
+
|
|
7
|
+
- `NotoSans-Regular.ttf` — SHA-256
|
|
8
|
+
`b85c38ecea8a7cfb39c24e395a4007474fa5a4fc864f6ee33309eb4948d232d5`
|
|
9
|
+
- `NotoSans-SemiBold.ttf` — SHA-256
|
|
10
|
+
`87a8b90ece1e89746b544e4e086f85a3710e41485a8078f9be874837dfad45d5`
|
|
11
|
+
|
|
12
|
+
Copyright 2018 The Noto Project Authors
|
|
13
|
+
(https://github.com/googlei18n/noto-fonts).
|
|
14
|
+
|
|
15
|
+
The fonts are licensed under the SIL Open Font License 1.1. The complete license
|
|
16
|
+
is distributed next to this notice as `NotoSans-OFL.txt`.
|
|
17
|
+
|
|
18
|
+
The native library uses these fixed bytes exclusively for Scene3D PNG text rasterization.
|
|
19
|
+
It does not scan or load system font directories at runtime.
|
|
20
|
+
|
|
21
|
+
The Rust core is source-pinned to fastPlotJs. Its dependency versions are
|
|
22
|
+
recorded in `rust/Cargo.lock`; the Python ABI adds a C-compatible wrapper
|
|
23
|
+
without Node or napi-rs. The original Node addon dependency license inventory
|
|
24
|
+
remains upstream history and does not describe the Python wheel.
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Source-shaped cartesian axis option validation before scene construction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import date, datetime, timezone
|
|
6
|
+
from math import isfinite, log10
|
|
7
|
+
from typing import Mapping
|
|
8
|
+
|
|
9
|
+
from .subplots import resolveTraceSubplotBindings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _js_text(value):
|
|
13
|
+
if value is None:
|
|
14
|
+
return "null"
|
|
15
|
+
if isinstance(value, bool):
|
|
16
|
+
return "true" if value else "false"
|
|
17
|
+
return str(value)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _normalize(value, axis):
|
|
21
|
+
scale = axis.get("scale", "linear")
|
|
22
|
+
if scale == "datetime":
|
|
23
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
24
|
+
if not isfinite(value):
|
|
25
|
+
raise ValueError("Invalid datetime numeric value")
|
|
26
|
+
return float(value)
|
|
27
|
+
if isinstance(value, datetime):
|
|
28
|
+
return value.timestamp() * 1000
|
|
29
|
+
if isinstance(value, date):
|
|
30
|
+
return datetime(value.year, value.month, value.day, tzinfo=timezone.utc).timestamp() * 1000
|
|
31
|
+
try:
|
|
32
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() * 1000
|
|
33
|
+
except (AttributeError, ValueError, OverflowError) as exc:
|
|
34
|
+
raise ValueError(f"Invalid datetime string value: {_js_text(value)}") from exc
|
|
35
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
36
|
+
if not isfinite(value):
|
|
37
|
+
raise ValueError("Invalid numeric value")
|
|
38
|
+
return float(value)
|
|
39
|
+
if isinstance(value, str):
|
|
40
|
+
categories = [item for item in axis.get("categories", []) if isinstance(item, str) and item]
|
|
41
|
+
if categories:
|
|
42
|
+
if value not in categories:
|
|
43
|
+
raise ValueError(f"Unknown categorical axis value: {value}")
|
|
44
|
+
return float(categories.index(value) + 1)
|
|
45
|
+
if isinstance(value, datetime):
|
|
46
|
+
return value.timestamp() * 1000
|
|
47
|
+
if isinstance(value, date):
|
|
48
|
+
return datetime(value.year, value.month, value.day, tzinfo=timezone.utc).timestamp() * 1000
|
|
49
|
+
try:
|
|
50
|
+
result = float(0 if value is None or value == "" else value)
|
|
51
|
+
except (TypeError, ValueError) as exc:
|
|
52
|
+
raise ValueError(f"Invalid numeric string value: {_js_text(value)}") from exc
|
|
53
|
+
if not isfinite(result):
|
|
54
|
+
raise ValueError(f"Invalid numeric string value: {_js_text(value)}")
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def validateAxisTickOptions(options):
|
|
59
|
+
axes = options.get("axes") or {}
|
|
60
|
+
for dimension in ("xAxes", "yAxes"):
|
|
61
|
+
for axis_id, axis in (axes.get(dimension) or {}).items():
|
|
62
|
+
prefix = f"axes.{dimension}.{axis_id}"
|
|
63
|
+
for field in ("labelPrefix", "labelSuffix"):
|
|
64
|
+
if field in axis and not isinstance(axis[field], str):
|
|
65
|
+
raise ValueError(f"{prefix}.{field} must be a string")
|
|
66
|
+
if "tickValues" not in axis:
|
|
67
|
+
continue
|
|
68
|
+
values = axis["tickValues"]
|
|
69
|
+
if not isinstance(values, list):
|
|
70
|
+
raise ValueError(f"{prefix}.tickValues must be an array")
|
|
71
|
+
if not values:
|
|
72
|
+
raise ValueError(f"{prefix}.tickValues must contain at least one value")
|
|
73
|
+
for value in values:
|
|
74
|
+
normalized = _normalize(value, axis)
|
|
75
|
+
if axis.get("scale") == "logarithmic":
|
|
76
|
+
if normalized <= 0:
|
|
77
|
+
raise ValueError("Logarithmic scale requires values > 0")
|
|
78
|
+
log10(normalized)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def validateRenderableAxisSides(options):
|
|
82
|
+
if not (options.get("axes") or {}).get("show"):
|
|
83
|
+
return
|
|
84
|
+
axes = options["axes"]
|
|
85
|
+
subplots = resolveTraceSubplotBindings(options)
|
|
86
|
+
seen = {"x": {}, "y": {}}
|
|
87
|
+
for binding in subplots:
|
|
88
|
+
for dimension, plural, default_side in (("x", "xAxes", "bottom"), ("y", "yAxes", "left")):
|
|
89
|
+
axis_id = binding[f"{dimension}AxisId"]
|
|
90
|
+
axis_map = axes.get(plural) or {}
|
|
91
|
+
if axis_id not in axis_map and axis_id != dimension:
|
|
92
|
+
raise ValueError(f"Unknown {dimension}-axis id '{axis_id}' for trace")
|
|
93
|
+
axis = axis_map.get(axis_id) or {}
|
|
94
|
+
if axis.get("show") is False:
|
|
95
|
+
continue
|
|
96
|
+
side = axis.get("side") or default_side
|
|
97
|
+
key = (binding["subplotId"], side)
|
|
98
|
+
seen[dimension].setdefault(key, set()).add(axis_id)
|
|
99
|
+
for dimension in ("x", "y"):
|
|
100
|
+
for (_, side), axis_ids in seen[dimension].items():
|
|
101
|
+
if len(axis_ids) > 1:
|
|
102
|
+
raise ValueError(f"Multiple visible {dimension}-axes on side '{side}' are not supported")
|
fastplotpy/camera3d.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Programmatic 3D camera operations ported from pinned core/camera3d.ts."""
|
|
2
|
+
from math import asin, cos, hypot, isfinite, pi, sin, tan
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _add(left, right):
|
|
6
|
+
return [a + b for a, b in zip(left, right)]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _subtract(left, right):
|
|
10
|
+
return [a - b for a, b in zip(left, right)]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _scale(vector, factor):
|
|
14
|
+
return [value * factor for value in vector]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _dot(left, right):
|
|
18
|
+
return sum(a * b for a, b in zip(left, right))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _cross(left, right):
|
|
22
|
+
return [left[1] * right[2] - left[2] * right[1],
|
|
23
|
+
left[2] * right[0] - left[0] * right[2],
|
|
24
|
+
left[0] * right[1] - left[1] * right[0]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _magnitude(vector):
|
|
28
|
+
return hypot(*vector)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _normalize(vector):
|
|
32
|
+
length = _magnitude(vector)
|
|
33
|
+
# The pinned source camera3d.ts rejects vectors at this magnitude.
|
|
34
|
+
return _scale(vector, 1 / length) if length > 1e-12 else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _rotate(vector, axis, radians):
|
|
38
|
+
cosine, sine = cos(radians), sin(radians)
|
|
39
|
+
return _add(_add(_scale(vector, cosine), _scale(_cross(axis, vector), sine)),
|
|
40
|
+
_scale(axis, _dot(axis, vector) * (1 - cosine)))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def orbitScene3dCamera(state, yawDegrees, pitchDegrees, maxPitchDegrees):
|
|
44
|
+
if not all(isfinite(value) for value in (yawDegrees, pitchDegrees, maxPitchDegrees)) or not 0 < maxPitchDegrees < 90:
|
|
45
|
+
return None
|
|
46
|
+
target = state["target"]
|
|
47
|
+
eye_offset = _subtract(state["eye"], target)
|
|
48
|
+
distance = _magnitude(eye_offset)
|
|
49
|
+
world_up = _normalize(state["up"])
|
|
50
|
+
if not distance > 1e-12 or world_up is None:
|
|
51
|
+
return None
|
|
52
|
+
yawed = _rotate(eye_offset, world_up, yawDegrees * pi / 180)
|
|
53
|
+
direction = _normalize(yawed)
|
|
54
|
+
if direction is None:
|
|
55
|
+
return None
|
|
56
|
+
current_pitch = asin(max(-1, min(1, _dot(direction, world_up)))) * 180 / pi
|
|
57
|
+
target_pitch = max(-maxPitchDegrees, min(maxPitchDegrees, current_pitch + pitchDegrees))
|
|
58
|
+
right = _normalize(_cross(world_up, yawed))
|
|
59
|
+
if right is None:
|
|
60
|
+
return None
|
|
61
|
+
pitched = _rotate(yawed, right, -(target_pitch - current_pitch) * pi / 180)
|
|
62
|
+
return {**state, "eye": _add(target, pitched)}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def dollyScene3dCamera(state, factor, limits):
|
|
66
|
+
if not isfinite(factor) or factor <= 0:
|
|
67
|
+
return None
|
|
68
|
+
if state["projection"] == "orthographic":
|
|
69
|
+
scale = max(limits["minOrthographicScale"],
|
|
70
|
+
min(limits["maxOrthographicScale"], state["orthographicScale"] * factor))
|
|
71
|
+
return {**state, "orthographicScale": scale}
|
|
72
|
+
target = state["target"]
|
|
73
|
+
offset = _subtract(state["eye"], target)
|
|
74
|
+
distance = _magnitude(offset)
|
|
75
|
+
if not distance > 1e-12:
|
|
76
|
+
return None
|
|
77
|
+
next_distance = max(limits["minDistance"], min(limits["maxDistance"], distance * factor))
|
|
78
|
+
return {**state, "eye": _add(target, _scale(offset, next_distance / distance))}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def panScene3dCamera(state, xRatio, yRatio, aspectRatio):
|
|
82
|
+
if not all(isfinite(value) for value in (xRatio, yRatio, aspectRatio)) or not aspectRatio > 0:
|
|
83
|
+
return None
|
|
84
|
+
eye, target = state["eye"], state["target"]
|
|
85
|
+
forward = _normalize(_subtract(target, eye))
|
|
86
|
+
world_up = _normalize(state["up"])
|
|
87
|
+
if forward is None or world_up is None:
|
|
88
|
+
return None
|
|
89
|
+
right = _normalize(_cross(forward, world_up))
|
|
90
|
+
if right is None:
|
|
91
|
+
return None
|
|
92
|
+
view_up = _normalize(_cross(right, forward))
|
|
93
|
+
if view_up is None:
|
|
94
|
+
return None
|
|
95
|
+
distance = _magnitude(_subtract(eye, target))
|
|
96
|
+
if not distance > 1e-12:
|
|
97
|
+
return None
|
|
98
|
+
vertical_span = (state["orthographicScale"] if state["projection"] == "orthographic"
|
|
99
|
+
else 2 * distance * tan(state["fieldOfViewDegrees"] * pi / 360))
|
|
100
|
+
if not isfinite(vertical_span) or not vertical_span > 0:
|
|
101
|
+
return None
|
|
102
|
+
translation = _add(_scale(right, -xRatio * vertical_span * aspectRatio),
|
|
103
|
+
_scale(view_up, yRatio * vertical_span))
|
|
104
|
+
return {**state, "eye": _add(eye, translation), "target": _add(target, translation)}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Color scale calculations ported from fastPlotJs core/colorScale.ts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from math import floor, isfinite, log10
|
|
6
|
+
from typing import Mapping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _hex(color):
|
|
10
|
+
if not isinstance(color, str):
|
|
11
|
+
return None
|
|
12
|
+
color = color.strip()
|
|
13
|
+
if len(color) == 4 and color.startswith("#"):
|
|
14
|
+
try:
|
|
15
|
+
return [int(digit * 2, 16) for digit in color[1:]]
|
|
16
|
+
except ValueError:
|
|
17
|
+
return None
|
|
18
|
+
if len(color) == 7 and color.startswith("#"):
|
|
19
|
+
try:
|
|
20
|
+
return [int(color[index:index + 2], 16) for index in (1, 3, 5)]
|
|
21
|
+
except ValueError:
|
|
22
|
+
return None
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def resolve_stops(raw, low, high, name):
|
|
27
|
+
if not raw:
|
|
28
|
+
return [{"offset": 0, "color": low}, {"offset": 1, "color": high}]
|
|
29
|
+
if len(raw) < 2:
|
|
30
|
+
raise ValueError(f"{name} must contain at least two stops")
|
|
31
|
+
stops = []
|
|
32
|
+
for index, stop in enumerate(raw):
|
|
33
|
+
if not isinstance(stop, Mapping):
|
|
34
|
+
raise ValueError(f"{name}[{index}] must be a color stop")
|
|
35
|
+
offset, color = stop.get("offset"), stop.get("color")
|
|
36
|
+
if isinstance(offset, bool) or not isinstance(offset, (int, float)) or not isfinite(offset) or not 0 <= offset <= 1:
|
|
37
|
+
raise ValueError(f"{name}[{index}].offset must be a finite number between 0 and 1")
|
|
38
|
+
if not isinstance(color, str) or not color.strip():
|
|
39
|
+
raise ValueError(f"{name}[{index}].color must be a non-empty string")
|
|
40
|
+
if _hex(color) is None:
|
|
41
|
+
raise ValueError(f"{name}[{index}].color must use #RGB or #RRGGBB format")
|
|
42
|
+
stops.append({"offset": offset, "color": color.strip()})
|
|
43
|
+
if stops[0]["offset"] != 0 or stops[-1]["offset"] != 1:
|
|
44
|
+
raise ValueError(f"{name} must start at offset 0 and end at offset 1")
|
|
45
|
+
if any(next_stop["offset"] <= stop["offset"] for stop, next_stop in zip(stops, stops[1:])):
|
|
46
|
+
raise ValueError(f"{name} offsets must be strictly increasing")
|
|
47
|
+
return stops
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def scale_ratio(value, low, high, scale="linear"):
|
|
51
|
+
if scale == "logarithmic":
|
|
52
|
+
if min(value, low, high) <= 0:
|
|
53
|
+
raise ValueError("Logarithmic color scales require values and bounds > 0")
|
|
54
|
+
if low == high:
|
|
55
|
+
return 1
|
|
56
|
+
value, low, high = log10(value), log10(low), log10(high)
|
|
57
|
+
elif low == high:
|
|
58
|
+
return 1
|
|
59
|
+
return max(0, min(1, (value - low) / (high - low)))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def interpolate(value, low, high, stops, scale="linear"):
|
|
63
|
+
ratio = scale_ratio(value, low, high, scale)
|
|
64
|
+
upper = next((index for index, stop in enumerate(stops) if ratio <= stop["offset"]), -1)
|
|
65
|
+
if upper <= 0:
|
|
66
|
+
start, end = stops[0], stops[1]
|
|
67
|
+
elif upper == -1:
|
|
68
|
+
start, end = stops[-2], stops[-1]
|
|
69
|
+
else:
|
|
70
|
+
start, end = stops[upper - 1], stops[upper]
|
|
71
|
+
first, last = _hex(start["color"]), _hex(end["color"])
|
|
72
|
+
if first is None or last is None:
|
|
73
|
+
return end["color"] if ratio >= end["offset"] else start["color"]
|
|
74
|
+
fraction = max(0, min(1, (ratio - start["offset"]) / (end["offset"] - start["offset"])))
|
|
75
|
+
channels = [floor(a + (b - a) * fraction + .5) for a, b in zip(first, last)]
|
|
76
|
+
return f"rgb({channels[0]}, {channels[1]}, {channels[2]})"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def legend_ticks(low, high, count, explicit, scale):
|
|
80
|
+
values = [value for value in explicit if low <= value <= high]
|
|
81
|
+
if values:
|
|
82
|
+
return values
|
|
83
|
+
if low == high:
|
|
84
|
+
return [low for _ in range(count)]
|
|
85
|
+
if scale == "logarithmic":
|
|
86
|
+
start, end = log10(low), log10(high)
|
|
87
|
+
return [10 ** (start + index / (count - 1) * (end - start)) for index in range(count)]
|
|
88
|
+
return [low + index / (count - 1) * (high - low) for index in range(count)]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mode": "grouped",
|
|
3
|
+
"orientation": "vertical",
|
|
4
|
+
"barWidthRatio": 0.72,
|
|
5
|
+
"minBarWidth": 1,
|
|
6
|
+
"borderWidth": 0,
|
|
7
|
+
"borderColor": "rgba(15, 23, 42, 0.35)",
|
|
8
|
+
"cornerRadius": 0,
|
|
9
|
+
"valueLabels": {
|
|
10
|
+
"show": false,
|
|
11
|
+
"color": "#0f172a",
|
|
12
|
+
"font": "11px Inter, system-ui, sans-serif",
|
|
13
|
+
"position": "center",
|
|
14
|
+
"offset": 4,
|
|
15
|
+
"decimals": 0,
|
|
16
|
+
"prefix": "",
|
|
17
|
+
"suffix": "",
|
|
18
|
+
"minBarWidth": 18,
|
|
19
|
+
"minBarHeight": 12,
|
|
20
|
+
"formatter": null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"orbit": true,
|
|
3
|
+
"dolly": true,
|
|
4
|
+
"pan": true,
|
|
5
|
+
"touch": true,
|
|
6
|
+
"keyboard": true,
|
|
7
|
+
"mouseButton": 0,
|
|
8
|
+
"panModifierKey": "shift",
|
|
9
|
+
"preventDefault": true,
|
|
10
|
+
"minDragDistance": 2,
|
|
11
|
+
"orbitDegreesPerPixel": 0.35,
|
|
12
|
+
"wheelDollySpeed": 0.0015,
|
|
13
|
+
"keyboardOrbitDegrees": 5,
|
|
14
|
+
"keyboardDollyFactor": 0.9,
|
|
15
|
+
"keyboardPanRatio": 0.05,
|
|
16
|
+
"minDistance": 0.25,
|
|
17
|
+
"maxDistance": 1000,
|
|
18
|
+
"minOrthographicScale": 0.01,
|
|
19
|
+
"maxOrthographicScale": 1000000,
|
|
20
|
+
"maxPitchDegrees": 89.5,
|
|
21
|
+
"maxInteractivePoints": 5000
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": false,
|
|
3
|
+
"mode": "xy",
|
|
4
|
+
"snap": "none",
|
|
5
|
+
"color": "#64748b",
|
|
6
|
+
"lineWidth": 1,
|
|
7
|
+
"lineDash": [4, 4],
|
|
8
|
+
"label": {
|
|
9
|
+
"show": false,
|
|
10
|
+
"font": "12px sans-serif",
|
|
11
|
+
"lineHeight": 18,
|
|
12
|
+
"color": "#ffffff",
|
|
13
|
+
"backgroundColor": "rgba(15, 23, 42, 0.92)",
|
|
14
|
+
"borderColor": "rgba(148, 163, 184, 0.8)",
|
|
15
|
+
"borderWidth": 1,
|
|
16
|
+
"padding": 6,
|
|
17
|
+
"offset": 8
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"valueScale": "linear",
|
|
3
|
+
"lowColor": "#e0f2fe",
|
|
4
|
+
"highColor": "#0369a1",
|
|
5
|
+
"colorStops": [],
|
|
6
|
+
"xCategories": [],
|
|
7
|
+
"yCategories": [],
|
|
8
|
+
"cellGap": 0,
|
|
9
|
+
"cornerRadius": 0,
|
|
10
|
+
"borderWidth": 0,
|
|
11
|
+
"borderColor": "rgba(15, 23, 42, 0.22)",
|
|
12
|
+
"labels": {
|
|
13
|
+
"show": false,
|
|
14
|
+
"font": "11px system-ui, sans-serif",
|
|
15
|
+
"color": "#475569",
|
|
16
|
+
"lineHeight": 14,
|
|
17
|
+
"offset": 10
|
|
18
|
+
},
|
|
19
|
+
"guides": {
|
|
20
|
+
"show": false,
|
|
21
|
+
"color": "rgba(100, 116, 139, 0.32)",
|
|
22
|
+
"lineWidth": 1
|
|
23
|
+
},
|
|
24
|
+
"missingCells": {
|
|
25
|
+
"show": false,
|
|
26
|
+
"fillColor": "rgba(148, 163, 184, 0.18)",
|
|
27
|
+
"borderColor": "rgba(100, 116, 139, 0.35)",
|
|
28
|
+
"borderWidth": 1
|
|
29
|
+
},
|
|
30
|
+
"cellLabels": {
|
|
31
|
+
"show": false,
|
|
32
|
+
"font": "11px system-ui, sans-serif",
|
|
33
|
+
"color": "#0f172a",
|
|
34
|
+
"autoContrast": true,
|
|
35
|
+
"lightTextColor": "#f8fafc",
|
|
36
|
+
"darkTextColor": "#0f172a",
|
|
37
|
+
"minimumContrastRatio": 4.5,
|
|
38
|
+
"lineHeight": 14,
|
|
39
|
+
"padding": 3,
|
|
40
|
+
"compactDecimals": 1,
|
|
41
|
+
"ellipsis": "…",
|
|
42
|
+
"decimals": 0,
|
|
43
|
+
"prefix": "",
|
|
44
|
+
"suffix": "",
|
|
45
|
+
"minCellWidth": 24,
|
|
46
|
+
"minCellHeight": 18,
|
|
47
|
+
"formatter": null
|
|
48
|
+
},
|
|
49
|
+
"colorScaleLegend": {
|
|
50
|
+
"show": false,
|
|
51
|
+
"position": "right",
|
|
52
|
+
"title": "",
|
|
53
|
+
"width": 12,
|
|
54
|
+
"height": 128,
|
|
55
|
+
"offset": 16,
|
|
56
|
+
"tickCount": 3,
|
|
57
|
+
"tickValues": [],
|
|
58
|
+
"tickSize": 6,
|
|
59
|
+
"labelGap": 6,
|
|
60
|
+
"labelPrefix": "",
|
|
61
|
+
"labelSuffix": "",
|
|
62
|
+
"decimals": 0,
|
|
63
|
+
"font": "11px system-ui, sans-serif",
|
|
64
|
+
"color": "#475569",
|
|
65
|
+
"lineHeight": 14,
|
|
66
|
+
"titleGap": 4,
|
|
67
|
+
"borderColor": "rgba(15, 23, 42, 0.28)",
|
|
68
|
+
"borderWidth": 1
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"margin": {
|
|
3
|
+
"l": 0,
|
|
4
|
+
"r": 0,
|
|
5
|
+
"t": 0,
|
|
6
|
+
"b": 0
|
|
7
|
+
},
|
|
8
|
+
"axisPaddingPercent": {
|
|
9
|
+
"start": 0,
|
|
10
|
+
"end": 0
|
|
11
|
+
},
|
|
12
|
+
"axisSide": {
|
|
13
|
+
"x": "bottom",
|
|
14
|
+
"y": "left"
|
|
15
|
+
},
|
|
16
|
+
"subplots": {
|
|
17
|
+
"rowGap": 16,
|
|
18
|
+
"columnGap": 20
|
|
19
|
+
},
|
|
20
|
+
"decorations": {
|
|
21
|
+
"titleFont": "600 18px system-ui, sans-serif",
|
|
22
|
+
"subtitleFont": "13px system-ui, sans-serif",
|
|
23
|
+
"titleColor": "#0f172a",
|
|
24
|
+
"subtitleColor": "#475569",
|
|
25
|
+
"plotAreaBackgroundColor": null,
|
|
26
|
+
"topPadding": 8,
|
|
27
|
+
"titleLineHeight": 22,
|
|
28
|
+
"subtitleLineHeight": 18,
|
|
29
|
+
"subtitleGap": 4,
|
|
30
|
+
"bottomGap": 8
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"show": false,
|
|
3
|
+
"position": "top-right",
|
|
4
|
+
"layout": "column",
|
|
5
|
+
"groupBy": "none",
|
|
6
|
+
"interactive": true,
|
|
7
|
+
"font": "12px system-ui, sans-serif",
|
|
8
|
+
"textLineHeight": 12,
|
|
9
|
+
"groupTitleFont": "600 12px system-ui, sans-serif",
|
|
10
|
+
"groupTitleLineHeight": 12,
|
|
11
|
+
"textColor": "#0f172a",
|
|
12
|
+
"groupTitleColor": "#334155",
|
|
13
|
+
"backgroundColor": "rgba(248, 250, 252, 0.68)",
|
|
14
|
+
"borderColor": "rgba(148, 163, 184, 0.7)",
|
|
15
|
+
"borderWidth": 1,
|
|
16
|
+
"itemLineWidth": 2,
|
|
17
|
+
"itemLineLength": 18,
|
|
18
|
+
"itemMarkerSize": 4,
|
|
19
|
+
"itemGap": 6,
|
|
20
|
+
"groupGap": 10,
|
|
21
|
+
"itemTextGap": 8,
|
|
22
|
+
"padding": 8,
|
|
23
|
+
"outsideGap": 8
|
|
24
|
+
}
|