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.
Files changed (78) hide show
  1. fastplotpy/README.md +70 -0
  2. fastplotpy/__init__.py +33 -0
  3. fastplotpy/assets/dist/assets/scene.worker-CcnexaKr.js +1 -0
  4. fastplotpy/assets/dist/index.js +15071 -0
  5. fastplotpy/assets/native/NotoSans-OFL.txt +96 -0
  6. fastplotpy/assets/native/THIRD_PARTY_NOTICES.md +24 -0
  7. fastplotpy/assets/native/fastplot_python_abi.dll +0 -0
  8. fastplotpy/assets/package.json +1 -0
  9. fastplotpy/axes_validation.py +102 -0
  10. fastplotpy/camera3d.py +104 -0
  11. fastplotpy/color_scale.py +88 -0
  12. fastplotpy/defaults/area-fill.json +5 -0
  13. fastplotpy/defaults/bar.json +22 -0
  14. fastplotpy/defaults/camera3d.json +22 -0
  15. fastplotpy/defaults/cartesian-references.json +10 -0
  16. fastplotpy/defaults/control.json +3 -0
  17. fastplotpy/defaults/crosshair.json +19 -0
  18. fastplotpy/defaults/error-bars.json +7 -0
  19. fastplotpy/defaults/heatmap.json +70 -0
  20. fastplotpy/defaults/interaction.json +3 -0
  21. fastplotpy/defaults/layout.json +32 -0
  22. fastplotpy/defaults/legend.json +24 -0
  23. fastplotpy/defaults/line-axes.json +28 -0
  24. fastplotpy/defaults/line3d.json +13 -0
  25. fastplotpy/defaults/markers.json +6 -0
  26. fastplotpy/defaults/mesh3d.json +13 -0
  27. fastplotpy/defaults/pan.json +7 -0
  28. fastplotpy/defaults/pie.json +27 -0
  29. fastplotpy/defaults/pie3d.json +7 -0
  30. fastplotpy/defaults/point-labels.json +13 -0
  31. fastplotpy/defaults/pointClick.json +6 -0
  32. fastplotpy/defaults/pointSelection.json +13 -0
  33. fastplotpy/defaults/polar.json +71 -0
  34. fastplotpy/defaults/polarRotate.json +5 -0
  35. fastplotpy/defaults/scatter3d.json +39 -0
  36. fastplotpy/defaults/scene3d.json +61 -0
  37. fastplotpy/defaults/surface.json +35 -0
  38. fastplotpy/defaults/tooltip.json +18 -0
  39. fastplotpy/defaults/trace-colors.json +10 -0
  40. fastplotpy/defaults/trace-options.json +24 -0
  41. fastplotpy/defaults/wonderplot.json +58 -0
  42. fastplotpy/defaults/zoom.json +12 -0
  43. fastplotpy/defaults.py +15 -0
  44. fastplotpy/error_bars.py +118 -0
  45. fastplotpy/exports.py +153 -0
  46. fastplotpy/families2d.py +896 -0
  47. fastplotpy/heatmap_labels.py +146 -0
  48. fastplotpy/interactions.py +243 -0
  49. fastplotpy/interactive.py +1922 -0
  50. fastplotpy/js_serialize.py +47 -0
  51. fastplotpy/lighting3d.py +113 -0
  52. fastplotpy/line3d_lod.py +138 -0
  53. fastplotpy/lod3d.py +105 -0
  54. fastplotpy/mesh3d_lod.py +107 -0
  55. fastplotpy/native.py +163 -0
  56. fastplotpy/overlays.py +561 -0
  57. fastplotpy/point_labels.py +86 -0
  58. fastplotpy/polar.py +445 -0
  59. fastplotpy/polygon_clip3d.py +148 -0
  60. fastplotpy/references.py +72 -0
  61. fastplotpy/rendering.py +65 -0
  62. fastplotpy/rust_scenes.py +314 -0
  63. fastplotpy/scene.py +1185 -0
  64. fastplotpy/scene3d.py +1215 -0
  65. fastplotpy/scene3d_accessibility.py +183 -0
  66. fastplotpy/scene3d_collection.py +158 -0
  67. fastplotpy/subplots.py +115 -0
  68. fastplotpy/surface_contours.py +122 -0
  69. fastplotpy/surface_lod.py +109 -0
  70. fastplotpy/svg3d.py +235 -0
  71. fastplotpy/trendline.py +236 -0
  72. fastplotpy/triangle_wireframe.py +51 -0
  73. fastplotpy/types.py +3027 -0
  74. fastplotpy-0.3.0a1.dist-info/METADATA +86 -0
  75. fastplotpy-0.3.0a1.dist-info/RECORD +78 -0
  76. fastplotpy-0.3.0a1.dist-info/WHEEL +5 -0
  77. fastplotpy-0.3.0a1.dist-info/licenses/LICENSE +21 -0
  78. 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.
@@ -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,5 @@
1
+ {
2
+ "show": false,
3
+ "opacity": 0.18,
4
+ "baseline": "axis-min"
5
+ }
@@ -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,10 @@
1
+ {
2
+ "lineColor": "#dc2626",
3
+ "lineWidth": 1.5,
4
+ "linePattern": "dashed",
5
+ "bandFillColor": "rgba(220, 38, 38, 0.12)",
6
+ "labelColor": "#991b1b",
7
+ "labelFont": "11px sans-serif",
8
+ "labelLineHeight": 14,
9
+ "labelOffset": 4
10
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "panStepRatio": 0.1
3
+ }
@@ -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,7 @@
1
+ {
2
+ "show": true,
3
+ "x": true,
4
+ "y": true,
5
+ "lineWidth": 1,
6
+ "capSize": 6
7
+ }
@@ -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,3 @@
1
+ {
2
+ "dragMode": "zoom"
3
+ }
@@ -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
+ }