-
Notifications
You must be signed in to change notification settings - Fork 481
Expand file tree
/
Copy pathfigure.py
More file actions
256 lines (219 loc) · 9.8 KB
/
figure.py
File metadata and controls
256 lines (219 loc) · 9.8 KB
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
253
254
255
256
# Copyright 2015 Bloomberg Finance L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
======
Figure
======
.. currentmodule:: bqplot.figure
.. autosummary::
:toctree: _generate/
Figure
"""
from traitlets import (
Bool, Unicode, Instance, List, Dict, Enum, Float, Int, TraitError, default,
validate
)
from ipywidgets import DOMWidget, register, widget_serialization
from bqscales import Scale, LinearScale
from .interacts import Interaction
from .marks import Mark
from .axes import Axis
from ._version import __frontend_version__
@register
class Figure(DOMWidget):
"""Main canvas for drawing a chart.
The Figure object holds the list of Marks and Axes. It also holds an
optional Interaction object that is responsible for figure-level mouse
interactions, the "interaction layer".
Besides, the Figure object has two reference scales, for positioning items
in an absolute fashion in the figure canvas.
Style Attributes
----------------
Attributes
----------
title: string (default: '')
title of the figure
axes: List of Axes (default: [])
list containing the instances of the axes for the figure
marks: List of Marks (default: [])
list containing the marks which are to be appended to the figure
interaction: Interaction or None (default: None)
optional interaction layer for the figure
scale_x: Scale
Scale representing the x values of the figure
scale_y: Scale
Scale representing the y values of the figure
padding_x: Float (default: 0.0)
Padding to be applied in the horizontal direction of the figure
around the data points, proportion of the horizontal length
padding_y: Float (default: 0.025)
Padding to be applied in the vertical direction of the figure
around the data points, proportion of the vertical length
legend_location: {'top-right', 'top', 'top-left', 'left',
'bottom-left', 'bottom', 'bottom-right', 'right'}
location of the legend relative to the center of the figure
background_style: Dict (default: {})
CSS style to be applied to the background of the figure
legend_style: Dict (default: {})
CSS style to be applied to the SVG legend e.g, {'fill': 'white'}
legend_text: Dict (default: {})
CSS style to be applied to the legend text e.g., {'font-size': 20}
title_style: Dict (default: {})
CSS style to be applied to the title of the figure
animation_duration: nonnegative int (default: 0)
Duration of transition on change of data attributes, in milliseconds.
Layout Attributes
-----------------
Attributes
----------
pixel_ratio:
Pixel ratio of the WebGL canvas (2 on retina screens). Set to 1 for better performance,
but less crisp edges. If set to None it will use the browser's window.devicePixelRatio.
display_toolbar: boolean (default: True)
Show or hide the integrated toolbar.
fig_margin: dict (default: {top=60, bottom=60, left=60, right=60})
Dictionary containing the top, bottom, left and right margins. The user
is responsible for making sure that the width and height are greater
than the sum of the margins.
auto_layout: boolean (default: False)
Whether to use the auto-layout solver or not
min_aspect_ratio: float
Minimum width / height ratio of the figure
max_aspect_ratio: float
Maximum width / height ratio of the figure
!!! Note
The aspect ratios stand for width / height ratios.
- If the available space is within bounds in terms of min and max aspect
ratio, we use the entire available space.
- If the available space is too oblong horizontally, we use the client
height and the width that corresponds max_aspect_ratio (maximize width
under the constraints).
- If the available space is too oblong vertically, we use the client width
and the height that corresponds to min_aspect_ratio (maximize height
under the constraint).
This corresponds to maximizing the area under the constraints.
Default min and max aspect ratio are both equal to 16 / 9.
"""
title = Unicode().tag(sync=True, display_name='Title')
axes = List(Instance(Axis)).tag(sync=True, **widget_serialization)
marks = List(Instance(Mark)).tag(sync=True, **widget_serialization)
interaction = Instance(Interaction, default_value=None,
allow_none=True).tag(sync=True,
**widget_serialization)
scale_x = Instance(Scale).tag(sync=True, **widget_serialization)
scale_y = Instance(Scale).tag(sync=True, **widget_serialization)
title_style = Dict(value_trait=Unicode()).tag(sync=True)
background_style = Dict().tag(sync=True)
legend_style = Dict().tag(sync=True)
legend_text = Dict().tag(sync=True)
theme = Enum(['classic', 'gg'], default_value='classic').tag(sync=True)
auto_layout = Bool(False).tag(sync=True)
min_aspect_ratio = Float(0.01).tag(sync=True)
max_aspect_ratio = Float(100).tag(sync=True)
pixel_ratio = Float(None, allow_none=True).tag(sync=True)
fig_margin = Dict(dict(top=60, bottom=60, left=60, right=60))\
.tag(sync=True)
padding_x = Float(0.0, min=0.0, max=1.0).tag(sync=True)
padding_y = Float(0.025, min=0.0, max=1.0).tag(sync=True)
legend_location = Enum(['top-right', 'top', 'top-left', 'left',
'bottom-left', 'bottom', 'bottom-right', 'right'],
default_value='top-right')\
.tag(sync=True, display_name='Legend position')
animation_duration = Int().tag(sync=True,
display_name='Animation duration')
display_toolbar = Bool(default_value=True).tag(sync=True)
def __init__(self, **kwargs):
super(Figure, self).__init__(**kwargs)
self._upload_png_callback = None
self._upload_svg_callback = None
self.on_msg(self._handle_custom_msgs)
@default('scale_x')
def _default_scale_x(self):
return LinearScale(min=0, max=1, allow_padding=False)
@default('scale_y')
def _default_scale_y(self):
return LinearScale(min=0, max=1, allow_padding=False)
def save_png(self, filename='bqplot.png', scale=None):
'''
Saves the Figure as a PNG file
Parameters
----------
filename: str (default: 'bqplot.png')
name of the saved file
scale: float (default: None)
Scale up the png resolution when scale > 1, when not given base this on the screen pixel ratio.
'''
self.send({'type': 'save_png', 'filename': filename, 'scale': scale})
def save_svg(self, filename='bqplot.svg'):
'''
Saves the Figure as an SVG file
Parameters
----------
filename: str (default: 'bqplot.svg')
name of the saved file
'''
self.send({"type": "save_svg", "filename": filename})
def get_png_data(self, callback, scale=None):
'''
Gets the Figure as a PNG memory view
Parameters
----------
callback: callable
Called with the PNG data as the only positional argument.
scale: float (default: None)
Scale up the png resolution when scale > 1, when not given base this on the screen pixel ratio.
'''
if self._upload_png_callback:
raise Exception('get_png_data already in progress')
self._upload_png_callback = callback
self.send({'type': 'upload_png', 'scale': scale})
def get_svg_data(self, callback):
'''
Gets the Figure as an SVG memory view (utf-8 encoded bytes).
Parameters
----------
callback: callable
Called with the SVG data (utf-8 encoded bytes) as the only positional argument.
'''
if self._upload_svg_callback:
raise Exception('get_svg_data already in progress')
self._upload_svg_callback = callback
self.send({'type': 'upload_svg'})
@validate('min_aspect_ratio', 'max_aspect_ratio')
def _validate_aspect_ratio(self, proposal):
value = proposal['value']
if proposal['trait'].name == 'min_aspect_ratio' and \
value > self.max_aspect_ratio:
raise TraitError('setting min_aspect_ratio > max_aspect_ratio')
if proposal['trait'].name == 'max_aspect_ratio' and \
value < self.min_aspect_ratio:
raise TraitError('setting max_aspect_ratio < min_aspect_ratio')
return value
def _handle_custom_msgs(self, _, content, buffers=None):
if content.get('event') == 'upload_png':
try:
self._upload_png_callback(buffers[0])
finally:
self._upload_png_callback = None
elif content.get('event') == 'upload_svg':
try:
self._upload_svg_callback(buffers[0])
finally:
self._upload_svg_callback = None
_view_name = Unicode('Figure').tag(sync=True)
_model_name = Unicode('FigureModel').tag(sync=True)
_view_module = Unicode('bqplot').tag(sync=True)
_model_module = Unicode('bqplot').tag(sync=True)
_view_module_version = Unicode(__frontend_version__).tag(sync=True)
_model_module_version = Unicode(__frontend_version__).tag(sync=True)