Skip to content

Commit e7457c5

Browse files
committed
changed IDObjects to Widget classes and introduced client side linking
1 parent 83b296e commit e7457c5

File tree

6 files changed

+95
-61
lines changed

6 files changed

+95
-61
lines changed

pandas3js/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
"""
77

8-
__version__ = '0.1.6'
8+
__version__ = '0.2.0'
99

1010
from pandas3js import models
1111
from pandas3js import views

pandas3js/models/idcollection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def change_by_df(self, df, columns=None,
248248
pass
249249
if key==otype_column:
250250
continue
251-
if not idobject.has_trait(key):
251+
if not key in idobject.get_object_trait_names():
252252
raise trait.TraitError('object with id {0} does not have trait: {1}'.format(s.id, key))
253253

254254
# wait to set traits until all are objects are tested
@@ -274,7 +274,7 @@ def trait_df(self, traits=None, incl_class=True):
274274
data = []
275275
for obj in self.idobjects:
276276
trait_dict = {}
277-
for name in obj.trait_names():
277+
for name in obj.get_object_trait_names():
278278
if traits is not None:
279279
if name not in traits:
280280
continue

pandas3js/models/idobject.py

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import uuid
99

1010
import traitlets as trait
11+
import ipywidgets as widgets
1112
from matplotlib import colors
1213
import pandas as pd
1314

@@ -38,18 +39,27 @@ def validate(self, obj, value):
3839

3940
return value
4041

41-
class IDObject(trait.HasTraits):
42+
class IDObject(widgets.Widget):
4243
""" an object with an id
4344
4445
"""
4546
id = HashableType()
4647
groups = trait.List(trait=trait.CUnicode(),default_value=("all",),
4748
help='the groups that this object belongs to')
48-
other_info = trait.CUnicode('',help='other information about the object as HTML')
49+
other_info = trait.CUnicode('',help='other information about the object as HTML').tag(sync=True)
4950

5051
@trait.default('id')
5152
def _default_id(self):
5253
return uuid.uuid4()
54+
55+
def get_object_trait_names(self):
56+
""" get trait names which are only associated with the object,
57+
i.e. not from the ipywidgets base class
58+
"""
59+
base_ipywidget_traits = set(widgets.Widget().trait_names())
60+
all_traits = set(self.trait_names())
61+
return list(all_traits.difference(base_ipywidget_traits))
62+
5363

5464
def trait_series(self):
5565
""" create pandas.Series of objects traits
@@ -65,7 +75,7 @@ def trait_series(self):
6575
6676
"""
6777
trait_dict = {}
68-
for name in self.trait_names():
78+
for name in self.get_object_trait_names():
6979
value = getattr(self, name)
7080
# might break series if cell value is a list
7181
value = tuple(value) if isinstance(value, list) else value
@@ -155,16 +165,16 @@ class GeometricObject(IDObject):
155165
(0.0, 0.0, 0.0)
156166
157167
"""
158-
position = Vector3(default_value=(0,0,0),help='cartesian coordinate of pivot')
168+
position = Vector3(default_value=(0,0,0),help='cartesian coordinate of pivot').tag(sync=True)
159169

160-
visible = trait.Bool(True)
161-
color = Color('red')
162-
transparency = trait.CFloat(1,min=0.0,max=1.0)
170+
visible = trait.Bool(True).tag(sync=True)
171+
color = Color('red').tag(sync=True)
172+
transparency = trait.CFloat(1,min=0.0,max=1.0).tag(sync=True)
163173

164-
label = trait.CUnicode('-')
165-
label_visible = trait.Bool(False)
166-
label_color = Color('red')
167-
label_transparency = trait.CFloat(1,min=0.0,max=1.0)
174+
label = trait.CUnicode('-').tag(sync=True).tag(sync=True)
175+
label_visible = trait.Bool(False).tag(sync=True)
176+
label_color = Color('red').tag(sync=True)
177+
label_transparency = trait.CFloat(1,min=0.0,max=1.0).tag(sync=True)
168178

169179
def default_viewmap(label_height=None):
170180
""" a wrapper to signal that all
@@ -204,7 +214,7 @@ class Sphere(GeometricObject):
204214
205215
206216
"""
207-
radius = trait.CFloat(1,min=0.)
217+
radius = trait.CFloat(1,min=0.).tag(sync=True)
208218

209219
# TODO orientation of default geometries
210220
@default_viewmap('height')
@@ -219,9 +229,9 @@ class Box(GeometricObject):
219229
(0.0, 0.0, 0.0)
220230
221231
"""
222-
width = trait.CFloat(1)
223-
height = trait.CFloat(1)
224-
depth = trait.CFloat(1)
232+
width = trait.CFloat(1).tag(sync=True)
233+
height = trait.CFloat(1).tag(sync=True)
234+
depth = trait.CFloat(1).tag(sync=True)
225235

226236
@default_viewmap('radius')
227237
class Octahedron(GeometricObject):
@@ -235,8 +245,8 @@ class Octahedron(GeometricObject):
235245
(0.0, 0.0, 0.0)
236246
237247
"""
238-
radius = trait.CFloat(1)
239-
detail = trait.CFloat(0)
248+
radius = trait.CFloat(1).tag(sync=True)
249+
detail = trait.CFloat(0).tag(sync=True)
240250

241251
@default_viewmap('radius')
242252
class Icosahedron(GeometricObject):
@@ -250,8 +260,8 @@ class Icosahedron(GeometricObject):
250260
(0.0, 0.0, 0.0)
251261
252262
"""
253-
radius = trait.CFloat(1)
254-
detail = trait.CFloat(0)
263+
radius = trait.CFloat(1).tag(sync=True)
264+
detail = trait.CFloat(0).tag(sync=True)
255265

256266
@default_viewmap('radius')
257267
class Circle(GeometricObject):
@@ -265,8 +275,8 @@ class Circle(GeometricObject):
265275
(0.0, 0.0, 0.0)
266276
267277
"""
268-
radius = trait.CFloat(1)
269-
segments = trait.CFloat(36)
278+
radius = trait.CFloat(1).tag(sync=True)
279+
segments = trait.CFloat(36).tag(sync=True)
270280

271281
class Plane(GeometricObject):
272282
""" a plane object
@@ -279,8 +289,8 @@ class Plane(GeometricObject):
279289
(0.0, 0.0, 0.0)
280290
281291
"""
282-
normal = Vector3(default_value=(0,0,1),help='the normal vector of the plane')
283-
width = trait.CFloat(1,min=0.0)
292+
normal = Vector3(default_value=(0,0,1),help='the normal vector of the plane').tag(sync=True)
293+
width = trait.CFloat(1,min=0.0).tag(sync=True)
284294

285295
@trait.validate('normal')
286296
def _valid_normal(self, proposal):
@@ -309,9 +319,9 @@ class Line(GeometricObject):
309319
310320
"""
311321

312-
end = Vector3(default_value=(1,1,1),help='cartesian coordinate of line end')
313-
end_color = Color('red')
314-
linewidth = trait.CFloat(1,min=0.0)
322+
end = Vector3(default_value=(1,1,1),help='cartesian coordinate of line end').tag(sync=True)
323+
end_color = Color('red').tag(sync=True)
324+
linewidth = trait.CFloat(1,min=0.0).tag(sync=True)
315325

316326
# TDOD only development version of PlainGeometry exposes face colors
317327
class TriclinicSolid(GeometricObject):
@@ -333,10 +343,10 @@ class TriclinicSolid(GeometricObject):
333343
pivot must be at the centre or corner
334344
335345
"""
336-
a = Vector3(default_value=(1,0,0),help='box vector a')
337-
b = Vector3(default_value=(0,1,0),help='box vector b')
338-
c = Vector3(default_value=(0,0,1),help='box vector c')
339-
pivot = trait.CUnicode('centre',help='pivot about centre or corner')
346+
a = Vector3(default_value=(1,0,0),help='box vector a').tag(sync=True)
347+
b = Vector3(default_value=(0,1,0),help='box vector b').tag(sync=True)
348+
c = Vector3(default_value=(0,0,1),help='box vector c').tag(sync=True)
349+
pivot = trait.CUnicode('centre',help='pivot about centre or corner').tag(sync=True)
340350

341351
@trait.validate('pivot')
342352
def _valid_pivot(self, proposal):
@@ -364,7 +374,7 @@ class TriclinicWire(TriclinicSolid):
364374
not valid
365375
366376
"""
367-
linewidth = trait.CFloat(1)
377+
linewidth = trait.CFloat(1).tag(sync=True)
368378

369379
# TODO Gimbal: add labels at end of each vector
370380
class Gimbal(GeometricObject):
@@ -390,13 +400,13 @@ class Gimbal(GeometricObject):
390400
not valid
391401
392402
"""
393-
a = Vector3(default_value=(1,0,0),help='vector a')
394-
b = Vector3(default_value=(0,1,0),help='vector b')
395-
c = Vector3(default_value=(0,0,1),help='vector c')
396-
a_color = Color('red')
397-
b_color = Color('green')
398-
c_color = Color('orange')
399-
linewidth = trait.CFloat(1,min=0.0)
403+
a = Vector3(default_value=(1,0,0),help='vector a').tag(sync=True)
404+
b = Vector3(default_value=(0,1,0),help='vector b').tag(sync=True)
405+
c = Vector3(default_value=(0,0,1),help='vector c').tag(sync=True)
406+
a_color = Color('red').tag(sync=True)
407+
b_color = Color('green').tag(sync=True)
408+
c_color = Color('orange').tag(sync=True)
409+
linewidth = trait.CFloat(1,min=0.0).tag(sync=True)
400410

401411

402412

pandas3js/views/jsgui.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def create_gui(geometry=None,callback=None,
2525
view=(10,-10,-10,10),fov=50,
2626
add_objects=True, add_labels=True,
2727
show_object_info=False,
28-
otype_column=None):
28+
otype_column=None, jslink=False):
2929
""" creates simple gui to visualise 3d geometry,
3030
with a callback to update geometry according to option widgets
3131
@@ -66,6 +66,9 @@ def create_gui(geometry=None,callback=None,
6666
add object labels to scene
6767
show_object_info : bool
6868
if True, show coordinate of object under mouse (currently only works for Perspective)
69+
jslink : bool
70+
if True, where possible, create client side links
71+
http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#The-difference-between-linking-in-the-kernel-and-linking-in-the-client
6972
7073
Returns
7174
-------
@@ -184,7 +187,7 @@ def create_gui(geometry=None,callback=None,
184187
else:
185188
gcollect = geometry
186189
scene = pjs.views.create_js_scene_view(gcollect,
187-
add_objects=add_objects,add_labels=add_labels)
190+
add_objects=add_objects,add_labels=add_labels, jslink=jslink)
188191
camera, renderer = pjs.views.create_jsrenderer(scene,
189192
orthographic=orthographic, camera_position=camera_position,
190193
view=view,fov=fov,

pandas3js/views/jsmesh.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import pythreejs as js
1313
import traitlets as trait
14+
import ipywidgets as widget
1415
from matplotlib import colors
1516
import numpy as np
1617

@@ -28,7 +29,7 @@ def trait_dlink(change):
2829
trait_dlink(None)
2930
return trait_dlink
3031

31-
def create_jsmesh_view(gobject,mapping=None):
32+
def create_jsmesh_view(gobject,mapping=None,jslink=False):
3233
"""create PyThreeJS Mesh for GeometricObjects
3334
and with one-way synchronisation
3435
@@ -37,13 +38,16 @@ def create_jsmesh_view(gobject,mapping=None):
3738
gobject : GeometricObject
3839
mapping : None or dict
3940
if None, use default gobject->jsobject mapping
41+
jslink : bool
42+
if True, where possible, create client side links
43+
http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#The-difference-between-linking-in-the-kernel-and-linking-in-the-client
4044
4145
Examples
4246
--------
4347
4448
>>> import pandas3js as pjs
4549
>>> sphere = pjs.models.Sphere()
46-
>>> mesh = create_jsmesh_view(sphere)
50+
>>> mesh = pjs.views.create_jsmesh_view(sphere)
4751
>>> mesh.position
4852
[0.0, 0.0, 0.0]
4953
>>> str(mesh.material.color)
@@ -67,6 +71,11 @@ def create_jsmesh_view(gobject,mapping=None):
6771
>>> mesh = create_jsmesh_view(pjs.models.Plane())
6872
6973
"""
74+
if jslink:
75+
direct_link = widget.jsdlink
76+
else:
77+
direct_link = trait.dlink
78+
7079
class_str = obj_to_str(gobject)
7180
if hasattr(gobject, '_use_default_viewmap'):
7281
class_map = copy.deepcopy(gobject_jsmapping['default'])
@@ -88,7 +97,7 @@ def create_jsmesh_view(gobject,mapping=None):
8897
for key, val in class_map['gvar'].items():
8998
geometry.set_trait(key, val)
9099
for key, val in class_map['gdmap'].items():
91-
trait.dlink((gobject, val), (geometry, key))
100+
direct_link((gobject, val), (geometry, key))
92101
for gkey, gdic in class_map['gfmap'].items():
93102
handle = _create_trait_dlink(gdic, gkey, gobject, geometry)
94103
gobject.observe(handle,names=gdic['vars'])
@@ -99,7 +108,7 @@ def create_jsmesh_view(gobject,mapping=None):
99108
for key, val in class_map['matvar'].items():
100109
material.set_trait(key, val)
101110
for key, val in class_map['matdmap'].items():
102-
trait.dlink((gobject, val), (material, key))
111+
direct_link((gobject, val), (material, key))
103112
for mkey, mdic in class_map['matfmap'].items():
104113
handle = _create_trait_dlink(mdic, mkey, gobject, material)
105114
gobject.observe(handle,names=mdic['vars'])
@@ -111,20 +120,21 @@ def create_jsmesh_view(gobject,mapping=None):
111120
for key, val in class_map['meshvar'].items():
112121
mesh.set_trait(key, val)
113122
for key, val in class_map['meshdmap'].items():
114-
trait.dlink((gobject, val), (mesh, key))
123+
direct_link((gobject, val), (mesh, key))
115124
for skey, sdic in class_map['meshfmap'].items():
116125
handle = _create_trait_dlink(sdic, skey, gobject, mesh)
117126
gobject.observe(handle,names=sdic['vars'])
118127

119128
# add special traits
120129
mesh.add_traits(gobject_id=HashableType())
121130
mesh.gobject_id = gobject.id
122-
mesh.add_traits(other_info=trait.CUnicode())
123-
trait.dlink((gobject, 'other_info'), (mesh, 'other_info'))
131+
mesh.add_traits(other_info=trait.CUnicode().tag(sync=True))
132+
direct_link((gobject, 'other_info'), (mesh, 'other_info'))
124133

125134
return mesh
126135

127-
def create_jslabelmesh_view(gobject, mapping=None):
136+
def create_jslabelmesh_view(gobject, mapping=None,
137+
jslink=False):
128138
"""create PyThreeJS Text Mesh for GeometricObject
129139
and with one-way synchronisation
130140
@@ -133,13 +143,16 @@ def create_jslabelmesh_view(gobject, mapping=None):
133143
gobject : GeometricObject
134144
mapping : None or dict
135145
if None, use default gobject->jsobject mapping
146+
jslink : bool
147+
if True, where possible, create client side links
148+
http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#The-difference-between-linking-in-the-kernel-and-linking-in-the-client
136149
137150
Examples
138151
--------
139152
140153
>>> import pandas3js as pjs
141154
>>> sphere = pjs.models.Sphere()
142-
>>> lmesh = create_jslabelmesh_view(sphere)
155+
>>> lmesh = pjs.views.create_jslabelmesh_view(sphere)
143156
>>> lmesh.position
144157
[0.0, 0.0, 0.0]
145158
>>> str(lmesh.material.map.string)
@@ -160,6 +173,11 @@ def create_jslabelmesh_view(gobject, mapping=None):
160173
[1.0, 3.0, 1.0]
161174
162175
"""
176+
if jslink:
177+
direct_link = widget.jsdlink
178+
else:
179+
direct_link = trait.dlink
180+
163181
class_str = obj_to_str(gobject)
164182
if hasattr(gobject, '_use_default_viewmap'):
165183
class_map = copy.deepcopy(gobject_jsmapping['default'])
@@ -189,19 +207,19 @@ def create_jslabelmesh_view(gobject, mapping=None):
189207
# add special traits
190208
mesh.add_traits(gobject_id=HashableType())
191209
mesh.gobject_id = gobject.id
192-
mesh.add_traits(other_info=trait.CUnicode())
210+
mesh.add_traits(other_info=trait.CUnicode().tag(sync=True))
193211

194212
if not class_map['show_label']:
195213
mesh.visible = False
196214
return mesh
197215

198216
# add directional synchronisation
199-
trait.dlink((gobject, 'other_info'), (mesh, 'other_info'))
200-
trait.dlink((gobject, 'label'), (text_map, 'string'))
201-
trait.dlink((gobject, 'position'), (mesh, 'position'))
202-
trait.dlink((gobject, 'label_visible'), (mesh, 'visible'))
217+
direct_link((gobject, 'other_info'), (mesh, 'other_info'))
218+
direct_link((gobject, 'label'), (text_map, 'string'))
219+
direct_link((gobject, 'position'), (mesh, 'position'))
220+
direct_link((gobject, 'label_visible'), (mesh, 'visible'))
221+
direct_link((gobject, 'label_transparency'), (material, 'opacity'))
203222
trait.dlink((gobject, 'label_color'), (text_map, 'color'), colors.to_hex)
204-
trait.dlink((gobject, 'label_transparency'), (material, 'opacity'))
205223
trait.dlink((gobject, 'label_transparency'), (material, 'transparent'),
206224
lambda t: True if t <= 0.999 else False)
207225

0 commit comments

Comments
 (0)