99
1010import numpy as np
1111
12- from traitlets import Bool , Bytes , CInt , Enum , Float , Instance , List , Unicode
12+ from traitlets import Bool , Bytes , CInt , Enum , Float , Instance , List , Unicode , TraitError , Union
1313
1414from ipywidgets import CallbackDispatcher , Color , DOMWidget , Image , Widget , widget_serialization
15- from ipywidgets .widgets .trait_types import bytes_serialization
15+ from ipywidgets .widgets .trait_types import (
16+ bytes_serialization , _color_names , _color_hex_re , _color_hexa_re , _color_rgbhsl_re
17+ )
1618
1719from ._frontend import module_name , module_version
1820
3133}
3234
3335
36+ # Traitlets does not allow validating without creating a trait class, so we need this
37+ def _validate_color (value ):
38+ if isinstance (value , str ):
39+ if (value .lower () in _color_names or _color_hex_re .match (value )
40+ or _color_hexa_re .match (value ) or _color_rgbhsl_re .match (value )):
41+ return value
42+ raise TraitError ('{} is not a valid HTML Color' .format (value ))
43+
44+
45+ def _validate_number (value , min_val , max_val ):
46+ try :
47+ number = float (value )
48+
49+ if number >= min_val and number <= max_val :
50+ return number
51+ except ValueError :
52+ raise TraitError ('{} is not a number' .format (value ))
53+ raise TraitError ('{} is not in the range [{}, {}]' .format (value , min_val , max_val ))
54+
55+
3456class Path2D (Widget ):
3557 """Create a Path2D.
3658
@@ -40,11 +62,8 @@ class Path2D(Widget):
4062
4163 _model_module = Unicode (module_name ).tag (sync = True )
4264 _model_module_version = Unicode (module_version ).tag (sync = True )
43- _view_module = Unicode (module_name ).tag (sync = True )
44- _view_module_version = Unicode (module_version ).tag (sync = True )
4565
4666 _model_name = Unicode ('Path2DModel' ).tag (sync = True )
47- _view_name = Unicode ('Path2DView' ).tag (sync = True )
4867
4968 value = Unicode (allow_none = False , read_only = True ).tag (sync = True )
5069
@@ -55,6 +74,76 @@ def __init__(self, value):
5574 super (Path2D , self ).__init__ ()
5675
5776
77+ class _CanvasGradient (Widget ):
78+ _model_module = Unicode (module_name ).tag (sync = True )
79+ _model_module_version = Unicode (module_version ).tag (sync = True )
80+
81+ x0 = Float (allow_none = False , read_only = True ).tag (sync = True )
82+ y0 = Float (allow_none = False , read_only = True ).tag (sync = True )
83+ x1 = Float (allow_none = False , read_only = True ).tag (sync = True )
84+ y1 = Float (allow_none = False , read_only = True ).tag (sync = True )
85+
86+ color_stops = List (allow_none = False , read_only = True ).tag (sync = True )
87+
88+ def __init__ (self , x0 , y0 , x1 , y1 , color_stops ):
89+ self .set_trait ('x0' , x0 )
90+ self .set_trait ('y0' , y0 )
91+ self .set_trait ('x1' , x1 )
92+ self .set_trait ('y1' , y1 )
93+
94+ for color_stop in color_stops :
95+ _validate_number (color_stop [0 ], 0 , 1 )
96+ _validate_color (color_stop [1 ])
97+ self .set_trait ('color_stops' , color_stops )
98+
99+ super (_CanvasGradient , self ).__init__ ()
100+
101+
102+ class LinearGradient (_CanvasGradient ):
103+ """Create a LinearGradient."""
104+ _model_name = Unicode ('LinearGradientModel' ).tag (sync = True )
105+
106+ def __init__ (self , x0 , y0 , x1 , y1 , color_stops ):
107+ """Create a LinearGradient object given the start point, end point and color stops.
108+
109+ Args:
110+ x0 (float): The x-axis coordinate of the start point.
111+ y0 (float): The y-axis coordinate of the start point.
112+ x1 (float): The x-axis coordinate of the end point.
113+ y1 (float): The y-axis coordinate of the end point.
114+ color_stops (list): The list of color stop tuples (offset, color) defining the gradient.
115+ """
116+ super (LinearGradient , self ).__init__ (x0 , y0 , x1 , y1 , color_stops )
117+
118+
119+ class RadialGradient (_CanvasGradient ):
120+ """Create a RadialGradient."""
121+ _model_name = Unicode ('RadialGradientModel' ).tag (sync = True )
122+
123+ r0 = Float (allow_none = False , read_only = True ).tag (sync = True )
124+ r1 = Float (allow_none = False , read_only = True ).tag (sync = True )
125+
126+ def __init__ (self , x0 , y0 , r0 , x1 , y1 , r1 , color_stops ):
127+ """Create a RadialGradient object given the start circle, end circle and color stops.
128+
129+ Args:
130+ x0 (float): The x-axis coordinate of the start circle.
131+ y0 (float): The y-axis coordinate of the start circle.
132+ r0 (float): The radius of the start circle.
133+ x1 (float): The x-axis coordinate of the end circle.
134+ y1 (float): The y-axis coordinate of the end circle.
135+ r1 (float): The radius of the end circle.
136+ color_stops (list): The list of color stop tuples (offset, color) defining the gradient.
137+ """
138+ _validate_number (r0 , 0 , float ('inf' ))
139+ _validate_number (r1 , 0 , float ('inf' ))
140+
141+ self .set_trait ('r0' , r0 )
142+ self .set_trait ('r1' , r1 )
143+
144+ super (RadialGradient , self ).__init__ (x0 , y0 , x1 , y1 , color_stops )
145+
146+
58147class _CanvasBase (DOMWidget ):
59148 _model_module = Unicode (module_name ).tag (sync = True )
60149 _model_module_version = Unicode (module_version ).tag (sync = True )
@@ -138,7 +227,7 @@ class Canvas(_CanvasBase):
138227 _view_name = Unicode ('CanvasView' ).tag (sync = True )
139228
140229 #: (valid HTML color) The color for filling rectangles and paths. Default to ``'black'``.
141- fill_style = Color ('black' )
230+ fill_style = Union (( Color (), Instance ( _CanvasGradient )), default_value = 'black' )
142231
143232 #: (valid HTML color) The color for rectangles and paths stroke. Default to ``'black'``.
144233 stroke_style = Color ('black' )
@@ -257,6 +346,33 @@ def sleep(self, time):
257346 """Make the Canvas sleep for `time` milliseconds."""
258347 self ._send_canvas_command (COMMANDS ['sleep' ], [time ])
259348
349+ # Gradient methods
350+ def create_linear_gradient (self , x0 , y0 , x1 , y1 , color_stops ):
351+ """Create a LinearGradient object given the start point, end point, and color stops.
352+
353+ Args:
354+ x0 (float): The x-axis coordinate of the start point.
355+ y0 (float): The y-axis coordinate of the start point.
356+ x1 (float): The x-axis coordinate of the end point.
357+ y1 (float): The y-axis coordinate of the end point.
358+ color_stops (list): The list of color stop tuples (offset, color) defining the gradient.
359+ """
360+ return LinearGradient (x0 , y0 , x1 , y1 , color_stops )
361+
362+ def create_radial_gradient (self , x0 , y0 , r0 , x1 , y1 , r1 , color_stops ):
363+ """Create a RadialGradient object given the start circle, end circle and color stops.
364+
365+ Args:
366+ x0 (float): The x-axis coordinate of the start circle.
367+ y0 (float): The y-axis coordinate of the start circle.
368+ r0 (float): The radius of the start circle.
369+ x1 (float): The x-axis coordinate of the end circle.
370+ y1 (float): The y-axis coordinate of the end circle.
371+ r1 (float): The radius of the end circle.
372+ color_stops (list): The list of color stop tuples (offset, color) defining the gradient.
373+ """
374+ return RadialGradient (x0 , y0 , r0 , x1 , y1 , r1 , color_stops )
375+
260376 # Rectangles methods
261377 def fill_rect (self , x , y , width , height = None ):
262378 """Draw a filled rectangle of size ``(width, height)`` at the ``(x, y)`` position."""
@@ -661,6 +777,10 @@ def __setattr__(self, name, value):
661777 super (Canvas , self ).__setattr__ (name , value )
662778
663779 if name in self .ATTRS :
780+ # If it's a Widget we need to serialize it
781+ if isinstance (value , Widget ):
782+ value = widget_serialization ['to_json' ](value , None )
783+
664784 self ._send_command ([COMMANDS ['set' ], [self .ATTRS [name ], value ]])
665785
666786 def _send_canvas_command (self , name , args = [], buffers = []):
0 commit comments