Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
538b567
Upgrade @cosmograph/cosmograph to 2.0.0-beta.18
Stukova Mar 13, 2025
2ade46b
Add API key support for Cosmograph (wip)
Stukova Apr 10, 2025
1fe984f
Enhance error handling and improve API key management
Stukova Apr 16, 2025
dae2e58
Upgrade @cosmograph/cosmograph to 2.0.0-beta.20
Stukova Apr 23, 2025
86039aa
Implement caching for Arrow table conversion
Stukova Apr 23, 2025
a2b4956
Upgrade to Cosmograph v2.0.0-beta.24, refactor project export flow
Stukova Jul 3, 2025
3aa1fef
Replace rectangular selection with polygonal selection
Stukova Jul 4, 2025
2b15631
Move fitViewButton and selectAreaButton to left top corner
Stukova Jul 4, 2025
d44e694
Reorganize legend layout to match Cosmograph app
Stukova Jul 9, 2025
d75e7cc
Upgrade to Cosmograph v2.0.0-beta.25
Stukova Jul 9, 2025
ac17c72
Reduce export logging and show project URL
Stukova Jul 10, 2025
7f05523
Add API key and export project documentation
Stukova Jul 10, 2025
f4710aa
Improve error handling and validation across widget module
Stukova Jul 10, 2025
ce5cd3a
Fix indentation and enable additional linting rules
Stukova Jul 12, 2025
143e372
Add null safety and input validation
Stukova Jul 12, 2025
792a501
Pre-release 0.0.47b0
Stukova Jul 12, 2025
cdb36e7
Upgrade cosmograph widget with enhanced labeling, selection, and disp…
Stukova Aug 20, 2025
47e4ad1
Upgrade to Cosmograph v2.0.0-beta.26
Stukova Aug 20, 2025
33bfd94
Handle undefined values in config property filtering
Stukova Aug 25, 2025
2d87edd
Release 1.0.0b0
Stukova Aug 26, 2025
58a9b4e
Simplify cosmo() API and add parameter docstrings
Stukova Oct 16, 2025
25c2d0c
Update dependencies and disable unsupported ingress test
Stukova Oct 16, 2025
56b41a1
Add Python 3.12 development environment configuration
Stukova Oct 16, 2025
2f1620e
Reorganize control buttons layout and fix zoom button styling
Stukova Oct 16, 2025
1f22c55
Support both string and string[] for linkTargetBy property
Stukova Oct 16, 2025
7e6fe61
Implement link timeline support
Stukova Oct 16, 2025
bdf37ff
Add loading indicator for Cosmograph widget initialization
Stukova Oct 17, 2025
5bc922a
Add settings panel and point info panel UI components
Stukova Oct 17, 2025
4d6ae81
Set scale_points_on_zoom to true by default
Stukova Oct 17, 2025
b854eca
Release 1.0.0b1
Stukova Oct 18, 2025
e459fb8
Set show_hovered_point_label to true by default
Stukova Oct 18, 2025
7b58848
Test export_project_by_name in Mobiuse example
Stukova Oct 18, 2025
bf61735
Add debug mode and fix phantom column refs in config
Stukova Oct 18, 2025
294b93b
Release 0.5.0b0
Stukova Oct 18, 2025
4d8bccb
Release 0.5.0
rokotyan Oct 21, 2025
ea8f966
Upgrade to Cosmograph v2.0.0-beta.28
Stukova Oct 22, 2025
658a6e8
Release 0.5.1
Stukova Oct 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cosmograph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from cosmograph.base import cosmo, base_cosmo
from cosmograph.widget import Cosmograph
from cosmograph.config import set_api_key, get_api_key

from importlib.metadata import version, PackageNotFoundError

Expand Down
9 changes: 1 addition & 8 deletions cosmograph/_dev_utils/data/config_prep/parsed_types.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
"optional": true
},
{
"name": "simulationCluster",
"name": "simulationClusterStrength",
"type": "number",
"description": "Cluster coefficient. Default `0.1`.",
"optional": true
Expand Down Expand Up @@ -728,13 +728,6 @@
"optional": true,
"default": "100"
},
{
"name": "showTopLabelsBy",
"type": "string",
"description": "Specify the numeric column that is used to determine the top points that will be sorted by. Works only when `pointLabelBy` is provided.",
"optional": true,
"default": "undefined"
},
{
"name": "pointLabelFn",
"type": "AccessorFn<string>",
Expand Down
13 changes: 11 additions & 2 deletions cosmograph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
cosmograph_base_signature,
)

from cosmograph.config import get_api_key

cosmo_base_sig = cosmograph_base_signature()
cosmo_base_params_doc_str = cosmograph_base_docs()

Expand Down Expand Up @@ -133,7 +135,7 @@ def cosmo(
point_size_by: str = None,
point_color_by: str = None,
point_color_palette: list[str] = None,
point_color_by_map: list[list[Union[str, list[float]]]] = None,
point_color_by_map: Dict[str, Union[str, list[float]]] = None,
point_color_strategy: str = None,
point_label_by: str = None,
point_color: Union[str, list[float]] = None,
Expand All @@ -144,6 +146,7 @@ def cosmo(
point_id_by: str = None,
point_index_by: str = None,
point_size_range: list[float] = None,
point_size_strategy: str = None,
point_label_weight_by: str = None,
point_cluster_by: str = None,
point_cluster_strength_by: str = None,
Expand Down Expand Up @@ -210,7 +213,6 @@ def cosmo(
show_labels_for: list[str] = None,
show_top_labels: bool = None,
show_top_labels_limit: int = None,
show_top_labels_by: str = None,
static_label_weight: float = None,
dynamic_label_weight: float = None,
label_margin: float = None,
Expand All @@ -219,6 +221,7 @@ def cosmo(
disable_link_width_legend: bool = None,
disable_point_color_legend: bool = None,
disable_link_color_legend: bool = None,
api_key: str = None,
clicked_point_index: int = None,
clicked_point_id: str = None,
selected_point_indices: list[int] = None,
Expand Down Expand Up @@ -296,6 +299,12 @@ def cosmo(
# validate the kwargs
kwargs = validate_kwargs(kwargs)

# If api_key is None, use the global one if available
if kwargs.get('api_key') is None:
global_api_key = get_api_key()
if global_api_key is not None:
kwargs['api_key'] = global_api_key

# Make a Cosmograph widget instance with these kwargs
return Cosmograph(**kwargs)

Expand Down
51 changes: 51 additions & 0 deletions cosmograph/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Configuration settings for cosmograph."""

# Global API key variable
_GLOBAL_API_KEY = None
_COSMOGRAPH_INSTANCES = set()


def set_api_key(api_key: str) -> None:
"""
Set a global API key to be used by all cosmograph instances.

Args:
api_key: The API key to use
"""
global _GLOBAL_API_KEY
_GLOBAL_API_KEY = api_key

# Update API key for all existing instances
for instance in _COSMOGRAPH_INSTANCES:
instance.api_key = api_key


def get_api_key() -> str:
"""
Get the current global API key.

Returns:
The current global API key, or None if not set
"""
return _GLOBAL_API_KEY


def register_instance(instance) -> None:
"""
Register a cosmograph instance to receive API key updates.

Args:
instance: A Cosmograph instance
"""
_COSMOGRAPH_INSTANCES.add(instance)


def unregister_instance(instance) -> None:
"""
Unregister a cosmograph instance.

Args:
instance: A Cosmograph instance
"""
if instance in _COSMOGRAPH_INSTANCES:
_COSMOGRAPH_INSTANCES.remove(instance)
110 changes: 96 additions & 14 deletions cosmograph/data/params_ssot.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,40 @@
"annotation":"bool",
"description":"Prevents the simulation from running, merely rendering the graph."
},
{
"name":"select_point_on_click",
"default": null,
"annotation":"bool",
"description":"Controls whether to select a clicked point with the connected points."
},
{
"name":"select_point_on_label_click",
"default": null,
"annotation":"bool",
"description":"Controls whether to select a point with the connected points when the point's label is clicked."
},
{
"name":"focus_point_on_click",
"default": null,
"annotation":"bool",
"description":"Controls whether to focus on a point when the point itself is clicked."
},
{
"name":"focus_point_on_label_click",
"default": null,
"annotation":"bool",
"description":"Controls whether to focus on a point when the point's label is clicked."
},
{
"name":"components_display_state_mode",
"default": null,
"annotation":"bool",
"description":"Controls display state messages behavior for all Cosmograph components."
},




{
"name":"simulation_decay",
"default":1000,
Expand Down Expand Up @@ -75,10 +109,10 @@
"description":"Sets simulation friction."
},
{
"name":"simulation_cluster",
"name":"simulation_cluster_strength",
"default":null,
"annotation":"float",
"description":""
"description":"Coefficient of simulation cluster strength from 0 to 1. If `undefined`, the graph will not apply any clustering force to the simulation."
},
{
"name":"background_color",
Expand Down Expand Up @@ -234,7 +268,7 @@
"description":"Activates quadtree algorithm for Many-Body force when set to True."
},
{
"name":"show_FPS_monitor",
"name":"show_fps_monitor",
"default":false,
"annotation":"bool",
"description":"Display an FPS counter in the upper right corner of the canvas."
Expand Down Expand Up @@ -338,7 +372,7 @@
{
"name":"point_color_by_map",
"default":null,
"annotation":"list[list[typing.Union[str, list[float]]]]",
"annotation":"typing.Dict[str, typing.Union[str, list[float]]]",
"description":"An optional mapping of values to colors for the points in the visualization. The keys in the map should be string and the values can be either color strings or arrays of RGBA values. Used when point_color_strategy is set to `'map'`."
},
{
Expand All @@ -359,6 +393,12 @@
"annotation":"list[float]",
"description":""
},
{
"name":"point_size_strategy",
"default":null,
"annotation":"str",
"description":"Specifies the strategy for sizing points based on data from the point_size_by column."
},
{
"name":"point_label_by",
"default":null,
Expand Down Expand Up @@ -498,34 +538,70 @@
"description":"Maximum number of top points to show labels for."
},
{
"name":"show_top_labels_by",
"name":"show_cluster_labels",
"default":false,
"annotation":"bool",
"description":"Show labels for the clusters based on the `pointClusterBy` column. When any point is selected, cluster labels will be hidden until the selection is cleared."
},
{
"name":"show_focused_point_label",
"default":null,
"annotation":"bool",
"description":"Controls whether to show the label for the focused point."
},
{
"name":"point_label_color",
"default":null,
"annotation":"str",
"description":"Column to determine which points are considered as a top."
"description":"Specifies the CSS color to use for the point labels."
},
{
"name":"point_label_font_size",
"default":13,
"annotation":"float",
"description":"Specifies the font size to use for the point labels."
},
{
"name":"cluster_label_font_size",
"default":16,
"annotation":"float",
"description":"Specifies the font size to use for the cluster labels."
},
{
"name":"scale_cluster_labels",
"default":true,
"annotation":"bool",
"description":"Whether to scale the cluster labels based on the number of points in the cluster."
},
{
"name":"static_label_weight",
"default":null,
"default":0.8,
"annotation":"float",
"description":"Weight of static labels."
"description":"Specifies the weight of the static labels."
},
{
"name":"dynamic_label_weight",
"default":null,
"default":0.7,
"annotation":"float",
"description":"Weight of dynamic labels."
"description":"Specifies the weight of the dynamic labels."
},
{
"name":"label_margin",
"default":null,
"default":5,
"annotation":"float",
"description":""
"description":"Specifies the margin between the label and the point."
},
{
"name":"label_padding",
"default":[6.5, 4.5, 6.5, 4.5],
"annotation":"list[float]",
"description":"Specifies the [left, top, right, bottom] padding of the label element."
},
{
"name":"show_hovered_point_label",
"default":null,
"default":false,
"annotation":"bool",
"description":"Flag to display the label for the currently hovered point."
"description":"Whether to show a hovered point label."
},
{
"name":"disable_point_size_legend",
Expand Down Expand Up @@ -598,5 +674,11 @@
"default":null,
"annotation":"typing.Callable[[typing.Dict[str, typing.Any]], typing.Any]",
"description":""
},
{
"name":"api_key",
"default":null,
"annotation":"str",
"description":"API key for authenticating with Cosmograph services. Can be set globally using set_api_key() function."
}
]
35 changes: 31 additions & 4 deletions cosmograph/widget/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

import pyarrow as pa
import anywidget
from traitlets import Bool, Float, List, Int, Unicode, Union, Bytes, Any, observe
from traitlets import Bool, Float, List, Int, Unicode, Union, Bytes, Any, observe, Dict

from .export_project import export_project
from cosmograph.config import register_instance, unregister_instance, get_api_key

meta_path = pathlib.Path(__file__).parent / "static" / "meta.json"

try:
with open(meta_path) as f:
meta_data = json.load(f)
Expand Down Expand Up @@ -107,13 +111,14 @@ class Cosmograph(anywidget.AnyWidget):
point_color_palette = List(Unicode, default_value=None, allow_none=True).tag(
sync=True
)
point_color_by_map = List(
List(Union([Unicode(), List(Float())])), default_value=None, allow_none=True
point_color_by_map = Dict(
Unicode(), Union([Unicode(), List(Float())]), default_value=None, allow_none=True
).tag(sync=True)
point_color_strategy = Unicode(None, allow_none=True).tag(sync=True)

point_size_by = Unicode(None, allow_none=True).tag(sync=True)
point_size_range = List(Float, default_value=None, allow_none=True).tag(sync=True)
point_size_strategy = Unicode(None, allow_none=True).tag(sync=True)
point_label_by = Unicode(None, allow_none=True).tag(sync=True)
point_label_weight_by = Unicode(None, allow_none=True).tag(sync=True)
point_x_by = Unicode(None, allow_none=True).tag(sync=True)
Expand Down Expand Up @@ -142,7 +147,7 @@ class Cosmograph(anywidget.AnyWidget):

show_labels = Bool(None, allow_none=True).tag(sync=True)
show_dynamic_labels = Bool(None, allow_none=True).tag(sync=True)
show_labels_for = List(Unicode, allow_none=True).tag(sync=True)
show_labels_for = List(Unicode, default_value=None, allow_none=True).tag(sync=True)
show_top_labels = Bool(None, allow_none=True).tag(sync=True)
show_top_labels_limit = Int(None, allow_none=True).tag(sync=True)
show_top_labels_by = Unicode(None, allow_none=True).tag(sync=True)
Expand Down Expand Up @@ -174,6 +179,9 @@ class Cosmograph(anywidget.AnyWidget):
clicked_point_id = Unicode(None, allow_none=True).tag(sync=True)
selected_point_indices = List(Int, allow_none=True).tag(sync=True)
selected_point_ids = List(Unicode, allow_none=True).tag(sync=True)
cosmograph_config = Dict(default_value={}, allow_none=True).tag(sync=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Avoid mutable default for cosmograph_config (risk of cross‑instance leaks).

Defaulting Dict to {} shares the same object across instances. Use None and initialize in init.

-    cosmograph_config = Dict(default_value={}, allow_none=True).tag(sync=True)
+    cosmograph_config = Dict(default_value=None, allow_none=True).tag(sync=True)

Initialize safely during construction:

         if self.api_key is None:
             global_api_key = get_api_key()
             if global_api_key is not None:
                 self.api_key = global_api_key
+
+        # Ensure per-instance dict (avoid shared mutable default)
+        if self.cosmograph_config is None:
+            self.cosmograph_config = {}

Also applies to: 338-343

🤖 Prompt for AI Agents
In cosmograph/widget/__init__.py around line 214 (and also apply the same change
to the similar block at lines 338-343), the trait Dict is initialized with a
mutable default_value {} which can be shared across instances; change the trait
to use default_value=None (keeping allow_none=True) and then in the class
__init__ detect if cosmograph_config is None and assign a new empty dict (or a
shallow copy) to self.cosmograph_config so each instance gets its own dict;
update both locations accordingly to avoid cross-instance state leakage.


api_key = Unicode(None, allow_none=True)

# Convert a Pandas DataFrame into a binary format and then write it to an IPC (Inter-Process Communication) stream.
# The `with` statement ensures that the IPC stream is properly closed after writing the data.
Expand Down Expand Up @@ -273,3 +281,22 @@ def step(self):

def capture_screenshot(self):
self.send({"type": "capture_screenshot"})

def export_project_by_name(self, project_name: str):
export_project(self.api_key, project_name, self.points, self.links, self.cosmograph_config)

def __init__(self, **kwargs):
super().__init__(**kwargs)
# Register this instance to receive API key updates
register_instance(self)

# If no API key was provided but global one exists, use it
if self.api_key is None:
global_api_key = get_api_key()
if global_api_key is not None:
self.api_key = global_api_key

def __del__(self):
# Clean up by unregistering when the instance is deleted
unregister_instance(self)
super().__del__()
Loading