scatter3d-anywidget 0.1.6__tar.gz → 0.1.7__tar.gz
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.
- {scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/PKG-INFO +1 -1
- {scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/pyproject.toml +1 -1
- {scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/src/scatter3d/scatter3d.py +34 -35
- {scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/src/scatter3d/widget_test.py +26 -5
- {scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/README.md +0 -0
- {scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/src/scatter3d/__init__.py +0 -0
- {scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/src/scatter3d/static/scatter3d.js +0 -0
- {scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/src/scatter3d/static/scatter3d.js.map +0 -0
|
@@ -632,12 +632,13 @@ class Scatter3dWidget(anywidget.AnyWidget):
|
|
|
632
632
|
|
|
633
633
|
@traitlets.observe("active_category_t")
|
|
634
634
|
def _on_active_category_t(self, change) -> None:
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
635
|
+
old = change.get("old")
|
|
636
|
+
new = change.get("new")
|
|
637
|
+
|
|
638
|
+
# Keep your existing policy/logic:
|
|
639
|
+
if self.interaction_mode_t == "lasso" and new is None:
|
|
638
640
|
old = change.get("old")
|
|
639
641
|
if old is not None:
|
|
640
|
-
# restore previous valid value to keep state consistent
|
|
641
642
|
self.set_trait("active_category_t", old)
|
|
642
643
|
raise traitlets.TraitError("active_category_t cannot be None in lasso mode")
|
|
643
644
|
|
|
@@ -647,15 +648,14 @@ class Scatter3dWidget(anywidget.AnyWidget):
|
|
|
647
648
|
if getattr(self, "_syncing_category", False):
|
|
648
649
|
return
|
|
649
650
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
651
|
+
# After our new policy, we should never be in lasso mode during a category switch,
|
|
652
|
+
# and active_category_t should be None. Still, be defensive: never crash.
|
|
653
|
+
if self.active_category_t is not None and self.active_category_t not in (
|
|
653
654
|
change.get("new") or []
|
|
654
655
|
):
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
)
|
|
656
|
+
self.active_category_t = None
|
|
657
|
+
if self.interactive_ready_t:
|
|
658
|
+
self.send_state("active_category_t")
|
|
659
659
|
|
|
660
660
|
@traitlets.observe("client_ready_t")
|
|
661
661
|
def _on_client_ready_t(self, change) -> None:
|
|
@@ -744,11 +744,9 @@ class Scatter3dWidget(anywidget.AnyWidget):
|
|
|
744
744
|
)
|
|
745
745
|
return v
|
|
746
746
|
|
|
747
|
-
# rotate mode
|
|
747
|
+
# rotate mode: do not crash on stale frontend values
|
|
748
748
|
if labels and v not in labels:
|
|
749
|
-
|
|
750
|
-
f"active_category_t={v!r} is not present in labels_t"
|
|
751
|
-
)
|
|
749
|
+
return None
|
|
752
750
|
return v
|
|
753
751
|
|
|
754
752
|
@property
|
|
@@ -874,11 +872,7 @@ class Scatter3dWidget(anywidget.AnyWidget):
|
|
|
874
872
|
|
|
875
873
|
self.category_editable_t = cat.editable
|
|
876
874
|
|
|
877
|
-
|
|
878
|
-
self.interaction_mode_t = "rotate"
|
|
879
|
-
if self.interactive_ready_t:
|
|
880
|
-
self.send_state("interaction_mode_t")
|
|
881
|
-
|
|
875
|
+
# coded values: uint16 bytes, length N
|
|
882
876
|
coded = cat.coded_values
|
|
883
877
|
if coded.shape[0] != self.num_points:
|
|
884
878
|
raise RuntimeError(
|
|
@@ -886,30 +880,17 @@ class Scatter3dWidget(anywidget.AnyWidget):
|
|
|
886
880
|
)
|
|
887
881
|
self.coded_values_t = self._pack_u16_c(coded)
|
|
888
882
|
|
|
883
|
+
# colors aligned with labels order
|
|
889
884
|
palette = cat.color_palette # label -> (r,g,b)
|
|
890
885
|
self.colors_t = [list(map(float, palette[lbl])) for lbl in cat.label_list]
|
|
891
886
|
|
|
887
|
+
# missing color
|
|
892
888
|
self.missing_color_t = list(map(float, cat.missing_color))
|
|
893
889
|
|
|
894
890
|
if len(self.colors_t) != len(self.labels_t):
|
|
895
891
|
raise RuntimeError(
|
|
896
892
|
"Internal error: colors_t length must match labels_t length"
|
|
897
893
|
)
|
|
898
|
-
|
|
899
|
-
# Now that labels/colors are consistent, enforce a stable policy for active category.
|
|
900
|
-
if self.interaction_mode_t == "rotate":
|
|
901
|
-
if (
|
|
902
|
-
self.active_category_t is not None
|
|
903
|
-
and self.active_category_t not in self.labels_t
|
|
904
|
-
):
|
|
905
|
-
# Professional + predictable: clear invalid selection on category switch.
|
|
906
|
-
self.active_category_t = None
|
|
907
|
-
if self.interactive_ready_t:
|
|
908
|
-
self.send_state("active_category_t")
|
|
909
|
-
else:
|
|
910
|
-
# lasso mode keeps existing behavior: ensure a valid active label exists.
|
|
911
|
-
self._ensure_active_category_invariants()
|
|
912
|
-
|
|
913
894
|
finally:
|
|
914
895
|
self._syncing_category = False
|
|
915
896
|
|
|
@@ -917,6 +898,11 @@ class Scatter3dWidget(anywidget.AnyWidget):
|
|
|
917
898
|
return self._category
|
|
918
899
|
|
|
919
900
|
def _set_category(self, category: Category) -> None:
|
|
901
|
+
# Idempotence: marimo may re-run cells and re-assign the same Category.
|
|
902
|
+
# That must be a no-op (must not clear active category, mode, etc).
|
|
903
|
+
if category is self._category:
|
|
904
|
+
return
|
|
905
|
+
|
|
920
906
|
if self._xyz is not None and category.num_values != self.num_points:
|
|
921
907
|
raise ValueError(
|
|
922
908
|
f"The number of values in the category ({category.num_values}) "
|
|
@@ -926,6 +912,19 @@ class Scatter3dWidget(anywidget.AnyWidget):
|
|
|
926
912
|
self._category.unsubscribe(self._category_cb_id)
|
|
927
913
|
|
|
928
914
|
self._category = category
|
|
915
|
+
|
|
916
|
+
# POLICY: changing the category object resets interaction to rotate and clears active.
|
|
917
|
+
# This must NOT run on Category mutations (coded values edits, palette tweaks, etc.).
|
|
918
|
+
if self.interaction_mode_t != "rotate":
|
|
919
|
+
self.interaction_mode_t = "rotate"
|
|
920
|
+
if self.interactive_ready_t:
|
|
921
|
+
self.send_state("interaction_mode_t")
|
|
922
|
+
|
|
923
|
+
if self.active_category_t is not None:
|
|
924
|
+
self.active_category_t = None
|
|
925
|
+
if self.interactive_ready_t:
|
|
926
|
+
self.send_state("active_category_t")
|
|
927
|
+
|
|
929
928
|
# Subscribe to new category
|
|
930
929
|
self._category_cb_id = category.subscribe(self._on_category_changed)
|
|
931
930
|
self._sync_traitlets_from_category()
|
|
@@ -18,7 +18,7 @@ def _():
|
|
|
18
18
|
points = np.random.randn(num_points, 3)
|
|
19
19
|
species_list = ["species1", "species2", "species3"]
|
|
20
20
|
species = random.choices(species_list, k=num_points)
|
|
21
|
-
species = Category(pandas.Series(species, name="species"))
|
|
21
|
+
species = Category(pandas.Series(species, name="species"), editable=False)
|
|
22
22
|
countries_list = ["country1", "country2", "country3"]
|
|
23
23
|
countries = random.choices(countries_list, k=num_points)
|
|
24
24
|
countries = Category(pandas.Series(countries, name="countries"))
|
|
@@ -29,12 +29,12 @@ def _():
|
|
|
29
29
|
w = Scatter3dWidget(xyz=points, category=species, point_ids=point_ids)
|
|
30
30
|
w.height = 800
|
|
31
31
|
ui = marimo.ui.anywidget(w)
|
|
32
|
-
return
|
|
32
|
+
return Scatter3dWidget, countries, ui, w
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@app.cell
|
|
36
|
-
def _(
|
|
37
|
-
category =
|
|
36
|
+
def _(countries):
|
|
37
|
+
category = countries
|
|
38
38
|
return (category,)
|
|
39
39
|
|
|
40
40
|
|
|
@@ -47,7 +47,28 @@ def _(category, ui, w):
|
|
|
47
47
|
|
|
48
48
|
@app.cell
|
|
49
49
|
def _(w):
|
|
50
|
-
|
|
50
|
+
widget = w
|
|
51
|
+
|
|
52
|
+
print("AFTER click:",
|
|
53
|
+
"mode=", widget.interaction_mode_t,
|
|
54
|
+
"editable=", widget.category_editable_t,
|
|
55
|
+
"active=", widget.active_category_t)
|
|
56
|
+
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.cell
|
|
61
|
+
def _():
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.cell
|
|
66
|
+
def _(Scatter3dWidget):
|
|
67
|
+
import scatter3d, inspect
|
|
68
|
+
|
|
69
|
+
print("scatter3d module:", scatter3d.__file__)
|
|
70
|
+
print("Scatter3dWidget source:", inspect.getsourcefile(Scatter3dWidget))
|
|
71
|
+
print("Scatter3dWidget._sync_traitlets_from_category line:", Scatter3dWidget._sync_traitlets_from_category.__code__.co_firstlineno)
|
|
51
72
|
return
|
|
52
73
|
|
|
53
74
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scatter3d_anywidget-0.1.6 → scatter3d_anywidget-0.1.7}/src/scatter3d/static/scatter3d.js.map
RENAMED
|
File without changes
|