Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

r""" 

Interacts for the Sage Jupyter notebook 

 

This is mostly the same as the stock ``ipywidgets.interact``, but with 

some customizations for Sage. 

 

TESTS: 

 

We need to setup a proper test environment for widgets:: 

 

sage: from ipywidgets.widgets.tests.utils import setup_test_comm 

sage: setup_test_comm() 

 

EXAMPLES:: 

 

sage: from sage.repl.ipython_kernel.interact import interact 

sage: @interact 

....: def f(x=(0,10)): 

....: pass 

Interactive function <function f at ...> with 1 widget 

x: IntSlider(value=5, description=u'x', max=10) 

sage: f.widget.children 

(IntSlider(value=5, description=u'x', max=10), Output()) 

""" 

 

#***************************************************************************** 

# Copyright (C) 2017 Jeroen Demeyer <jdemeyer@cage.ugent.be> 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 2 of the License, or 

# (at your option) any later version. 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

 

from ipywidgets.widgets import SelectionSlider, ValueWidget, ToggleButtons 

from ipywidgets.widgets.interaction import interactive, signature, ValueWidget 

from copy import copy 

from collections import Iterable, Iterator 

from .widgets import EvalText, SageColorPicker 

from sage.structure.element import parent 

from sage.symbolic.ring import SR 

from sage.plot.colors import Color 

 

 

class sage_interactive(interactive): 

""" 

Wrapper around the ipywidgets interactive which handles some SageNB 

specifics. 

 

EXAMPLES:: 

 

sage: from sage.repl.ipython_kernel.interact import sage_interactive 

sage: def myfunc(x=10, y="hello", z=None): pass 

sage: sage_interactive(myfunc, x=(0,100), z=["one", "two", "three"]) 

Interactive function <function myfunc at ...> with 3 widgets 

x: IntSlider(value=10, description=u'x') 

y: Text(value=u'hello', description=u'y') 

z: Dropdown(description=u'z', options=('one', 'two', 'three'), value=None) 

""" 

def __init__(*args, **kwds): 

""" 

See :class:`ipywidgets.widgets.interaction.interactive` 

 

TESTS:: 

 

sage: from sage.repl.ipython_kernel.interact import sage_interactive 

sage: def myfunc(): pass 

sage: sage_interactive(myfunc, dict(manual=True)) 

Manual interactive function <function myfunc ...> with 0 widgets 

 

:: 

 

sage: def myfunc(auto_update=False): pass 

sage: sage_interactive(myfunc) 

Manual interactive function <function myfunc ...> with 0 widgets 

sage: def myfunc(auto_update=None): pass 

sage: sage_interactive(myfunc) 

Interactive function <function myfunc ...> with 0 widgets 

""" 

# Use *args to avoid name clash with keyword arguments 

if len(args) < 3: 

(self, f) = args 

options = {} 

else: 

(self, f, options) = args 

options = options.copy() 

 

# Check for auto_update in signature 

sig = signature(f) 

params = copy(sig.parameters) 

try: 

p_auto_update = params.pop("auto_update") 

except KeyError: 

pass 

else: 

options["manual"] = (p_auto_update.default is False) 

 

self.__signature = sig.replace(parameters=params.values()) 

super(sage_interactive, self).__init__(f, options, **kwds) 

if self.manual: 

# In Sage, manual interacts are always run once 

self.on_displayed(self.update) 

else: 

# In automatic mode, clicking on a ToggleButtons button 

# should also run the interact 

for widget in self.kwargs_widgets: 

if isinstance(widget, ToggleButtons): 

widget.on_msg(self.update) 

 

def __repr__(self): 

""" 

Textual representation of this interactive function. 

 

EXAMPLES:: 

 

sage: from sage.repl.ipython_kernel.interact import sage_interactive 

sage: def myfunc(): pass 

sage: sage_interactive(myfunc) 

Interactive function <function myfunc ...> with 0 widgets 

""" 

s = "Manual interactive" if self.manual else "Interactive" 

widgets = [w for w in self.children if isinstance(w, ValueWidget)] 

n = len(widgets) 

s += " function %r with %s widget%s" % ( 

self.f, n, "s" if n != 1 else "") 

for w in widgets: 

s += "\n %s: %s" % (w._kwarg, w) 

return s 

 

def signature(self): 

""" 

Return the fixed signature of the interactive function (after 

a possible ``auto_update`` parameter was removed). 

 

EXAMPLES:: 

 

sage: from sage.repl.ipython_kernel.interact import sage_interactive 

sage: def myfunc(x=[1,2,3], auto_update=False): pass 

sage: sage_interactive(myfunc).signature().parameters # py2 

OrderedDict([('x', <Parameter ... 'x'>)]) 

sage: sage_interactive(myfunc).signature().parameters # py3 

mappingproxy({'x': <Parameter "x=[1, 2, 3]">}) 

""" 

return self.__signature 

 

@classmethod # Behaves like a staticmethod, but we need super() 

def widget_from_single_value(cls, abbrev, *args, **kwds): 

""" 

Convert a single value (i.e. a non-iterable) to a widget. 

 

This supports the Sage :class:`Color` class. Any unknown type 

is changed to a string for evaluating. This is meant to support 

symbolic expressions like ``sin(x)``. 

 

EXAMPLES:: 

 

sage: from sage.repl.ipython_kernel.interact import sage_interactive 

sage: sage_interactive.widget_from_single_value("sin(x)") 

Text(value=u'sin(x)') 

sage: sage_interactive.widget_from_single_value(sin(x)) 

EvalText(value=u'sin(x)') 

sage: from sage.plot.colors import Color 

sage: sage_interactive.widget_from_single_value(Color('cornflowerblue')) 

SageColorPicker(value='#6495ed') 

""" 

# Support Sage Colors 

if isinstance(abbrev, Color): 

return SageColorPicker(value=abbrev.html_color()) 

# Get widget from IPython if possible 

widget = super(sage_interactive, cls).widget_from_single_value(abbrev, *args, **kwds) 

if widget is not None or isinstance(abbrev, Iterable): 

return widget 

# If IPython didn't construct a widget and the abbrev is not an 

# iterable, return an EvalText widget 

return EvalText(value=str(abbrev)) 

 

@classmethod # Behaves like a staticmethod, but we need super() 

def widget_from_tuple(cls, abbrev, *args, **kwds): 

""" 

Convert a tuple to a widget. 

 

This supports two SageNB extensions: ``(description, abbrev)`` 

if ``description`` is a string and ``(default, abbrev)`` if 

``abbrev`` is not a single value. 

 

Symbolic expressions are changed to a floating-point number. 

 

EXAMPLES:: 

 

sage: from sage.repl.ipython_kernel.interact import sage_interactive 

sage: sage_interactive.widget_from_tuple( (0, 10) ) 

IntSlider(value=5, max=10) 

sage: sage_interactive.widget_from_tuple( ("number", (0, 10)) ) 

IntSlider(value=5, description=u'number', max=10) 

sage: sage_interactive.widget_from_tuple( (3, (0, 10)) ) 

IntSlider(value=3, max=10) 

sage: sage_interactive.widget_from_tuple( (2, dict(one=1, two=2, three=3)) ) 

Dropdown(index=1, options={'three': 3, 'two': 2, 'one': 1}, value=2) 

sage: sage_interactive.widget_from_tuple( (sqrt(2), pi) ) 

FloatSlider(value=2.277903107981444, max=3.141592653589793, min=1.4142135623730951) 

""" 

# Support (description, abbrev) 

if len(abbrev) == 2 and isinstance(abbrev[0], str): 

widget = cls.widget_from_abbrev(abbrev[1]) 

widget.description = abbrev[0] 

return widget 

# Support (default, abbrev) 

if len(abbrev) == 2 and isinstance(abbrev[1], Iterable): 

widget = cls.widget_from_abbrev(abbrev[1]) 

widget.value = abbrev[0] 

return widget 

# Numerically evaluate symbolic expressions 

def n(x): 

if parent(x) is SR: 

return x.numerical_approx() 

else: 

return x 

abbrev = tuple(n(x) for x in abbrev) 

return super(sage_interactive, cls).widget_from_tuple(abbrev, *args, **kwds) 

 

@classmethod # Behaves like a staticmethod, but we need super() 

def widget_from_iterable(cls, abbrev, *args, **kwds): 

""" 

Convert an unspecified iterable to a widget. 

 

This behaves like in ipywidgets, except that an iterator (like 

a generator object) becomes a ``SelectionSlider``. 

 

EXAMPLES:: 

 

sage: from sage.repl.ipython_kernel.interact import sage_interactive 

sage: sage_interactive.widget_from_iterable([1..5]) 

Dropdown(options=(1, 2, 3, 4, 5), value=1) 

sage: sage_interactive.widget_from_iterable(iter([1..5])) 

SelectionSlider(options=(1, 2, 3, 4, 5), value=1) 

sage: sage_interactive.widget_from_iterable((1..5)) 

SelectionSlider(options=(1, 2, 3, 4, 5), value=1) 

sage: sage_interactive.widget_from_iterable(x for x in [1..5]) 

SelectionSlider(options=(1, 2, 3, 4, 5), value=1) 

sage: def gen(): 

....: yield 1; yield 2; yield 3; yield 4; yield 5 

sage: sage_interactive.widget_from_iterable(gen()) 

SelectionSlider(options=(1, 2, 3, 4, 5), value=1) 

""" 

if isinstance(abbrev, Iterator): 

return SelectionSlider(options=list(abbrev)) 

return super(sage_interactive, cls).widget_from_iterable(abbrev, *args, **kwds) 

 

 

# @interact decorator 

interact = sage_interactive.factory()