Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3e4fdf3

Browse files
Display backends rework (#539)
* draft * remove duplicates on src sens getters * add nested coll support for mpl * support nested collection for plotly * add setters * update set_children_styles * update collection.remove to be search recursively * add describe method * adapt getBH to nested collections * remove irrelevant todos * add TODOS * remove print statement * add self to collection.describe() * add parent/child philosophy * fix regression * add override_parent to Collection * fix __add__ * add collection repr, also nested * fix remove and make private * testbug fix * mini syntax sugar * fix nested rotation * remove unused imports * mini color fix * rework collection.describe() * fix parent setter * fix override * fix plus, remove minus * add describe and repr_html to all objects * add properties * prettify * fix collection setter * docstring * docstrings * + - behavior fixing * replace labels with desc * add basic tests * include errors='raise' in remove * only *arg object inputs for collections * removed Collection "special" repr * remove allows *args * draft * fixing and adding tests * remove now useless radd * fix copy parent bug * fix tests and misc * refactoring * completing tests, full coverage * pylint * meaningful renaming * collection and parent docstrings * use orient concat on rotated sensors * docstings + long forgotten properties * make compatible with python 3.7 * changelog and pull from main * remove describe method * will be added in another more complete PR * remove unused import * coverage * remove print statement * pylint * add pixel pretty describe * allow different pix shapes with pixel_agg * add input check * fix input checks * fix orientation tiling&repeating * mini bug fix * add tests * fix input_checks syntax test * fix plotly mulitidimensional pixel display * describe, repr, tests and changelog * draft * remove print statement * collection setter override parents * remove print statement * refactor getBHdict * remove obsolete comment * pylint * core function argument consistency * 'field' is now a positional arg only * field and observer always in pos 1 and 2 * remove return self of reset() * allow collection/add/remove flat list input * take reset change back * complete docs * remove introduction from init * implement field func * fix docs * coverage * coverage * extend pixel_agg testing * fix bad tiling bug * add test * fix example * fix tile_group_property * remove print statement * make tiling vs superposition test more general * make tiling vs superposition test more general * fix current_line_field call * fix pixel_agg * finish merge * add pandas * draft * typo * pylint * add pandas * update docstrings * install .dev on circleci * code style * fix tests * docstring * move getBHdict module into level2 getBH * refactoring * move matplotlib folder * remove double input checks * refactoring * pyvista draft implementation * remove scalar bar * fix remove scalar bar * typo * add back animation * add matplotlib auto backend * add colorscale caching * remove print statement * add cached colorscale to colormap translator * add facecolor subdivision for matplotlib_auto * remove bad import * add mag arrows for generic traces * run precommit * fix subdivide mesh by facecolor * renaming * make animation generic * handle markers in main show function * pylint * draft matplotlib_auto animation * add return animation object option * archive matplotlib old * make extra trace generic * remove pyvista (now generic branch) * animation kwargs handling bug fix * simplify structure and avoid redundancy * fix import paths * remove superfluous nan value in scatter mag arrow * pylint * lgtm * add symb and line style translation for mpl * fix animation with user axes * fix output dataframe ignored * add tests * add changelog entry * add example * black * test cov * pylint * fix traces grouping * draft extra model3d compatibility * fix tests * modify for generic backend * document generic backend traces * fix bad arrow orientation with (0,0-1) * minibug f-string fix * docstrings minifix * modify introduction * fix dataframe output with pixel_agg * remove unused test * refactoring * typo * move line tiling to field function * avoid tiling warning with vertices with diff len * refactor field_func * Register field functions * automatize imports * automatize imports * back to explicit imports * renaming modules finish * refactor param dims properties * merge get_BH_wrap functions into one file * pylint * update backend docstrings * register kind * add matplotlib numbering * return_fig, update_layout and layout * fig show bug fix plotly * Merge branch 'display-backend-rework' of https://github.com/magpylib/magpylib into refactor-getBH-02 * Merge branch 'display-backend-rework' of https://github.com/magpylib/magpylib into refactor-getBH-02 * fix bad pull from display-rework * pylint * draft getbdict vert feature * fix tests * pull from refactor_getbh * pylint * fix getB dict Line with "scalar" vertices * fix mag show bug * update compound example with generic extra trace * remove unused files * coverage * fix ragged seq tiling for gebhdict line * coverage * coverage * update changelog * update changelog * frame duration for mpl * refactor inheritance * move field func logic to BaseSource class * refactor Registering classes with a metaclass * fix subclassing sideeffect with metaclass e.g. subclasssing Cuboid with MyCuboid, adds MyCuboid but removes Cuboid * pylint cycling import fix * finish pull from refactor-getBH-temp * coverage * small refactoring of magnet and current style Co-authored-by: Boisselet Alexandre (IFAT DC ATV SC D TE2) <[email protected]> Co-authored-by: Michael Ortner <[email protected]>
1 parent a12090d commit 3e4fdf3

37 files changed

+2112
-2626
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ All notable changes to magpylib are documented here.
66
# Unreleased
77
* Field computation `getB`/`getH` now supports 2D [pandas](https://pandas.pydata.org/).[dataframe](https://pandas.pydata.org/docs/user_guide/dsintro.html#dataframe) in addition to the `numpy.ndarray` as output type. ([#523](https://github.com/magpylib/magpylib/pull/523))
88
* Internal `getB`/`getH` refactoring. Like for the object oriented interface, the direct interface for `'Line'` current now also accepts `'vertices'` as argument. ([#540](https://github.com/magpylib/magpylib/pull/540))
9+
* Complete plotting backend rework to prepare for easy implementation of new backends, with minimal maintenance. ([#539](https://github.com/magpylib/magpylib/pull/539))
910

1011
## [4.0.4] - 2022-06-09
1112

docs/examples/examples_13_3d_models.md

Lines changed: 36 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ kernelspec:
1818
(examples-own-3d-models)=
1919
## Custom 3D models
2020

21-
Each Magpylib object has a default 3D representation that is displayed with `show`. Users can add a custom 3D model to any Magpylib object with help of the `style.model3d.add_trace` method. The new trace is stored in `style.model3d.data`. User-defined traces move with the object just like the default models do. The default trace can be hidden with the command `obj.model3d.showdefault=False`.
21+
Each Magpylib object has a default 3D representation that is displayed with `show`. Users can add a custom 3D model to any Magpylib object with help of the `style.model3d.add_trace` method. The new trace is stored in `style.model3d.data`. User-defined traces move with the object just like the default models do. The default trace can be hidden with the command `obj.model3d.showdefault=False`. When using the `'generic'` backend, custom traces are automatically translated into any other backend. If a specific backend is used, it will only show when called with the corresponding backend.
2222

2323
The input `trace` is a dictionary which includes all necessary information for plotting or a `magpylib.graphics.Trace3d` object. A `trace` dictionary has the following keys:
2424

25-
1. `'backend'`: `'matplotlib'` or `'plotly'`
25+
1. `'backend'`: `'generic'`, `'matplotlib'` or `'plotly'`
2626
2. `'constructor'`: name of the plotting constructor from the respective backend, e.g. plotly `'Mesh3d'` or matplotlib `'plot_surface'`
2727
3. `'args'`: default `None`, positional arguments handed to constructor
2828
4. `'kwargs'`: default `None`, keyword arguments handed to constructor
29-
5. `'coordsargs'`: tells magpylib which input corresponds to which coordinate direction, so that geometric representation becomes possible. By default `{'x': 'x', 'y': 'y', 'z': 'z'}` for the Plotly backend and `{'x': 'args[0]', 'y': 'args[1]', 'z': 'args[2]'}` for the Matplotlib backend.
29+
5. `'coordsargs'`: tells magpylib which input corresponds to which coordinate direction, so that geometric representation becomes possible. By default `{'x': 'x', 'y': 'y', 'z': 'z'}` for the `'generic'` backend and Plotly backend, and `{'x': 'args[0]', 'y': 'args[1]', 'z': 'args[2]'}` for the Matplotlib backend.
3030
6. `'show'`: default `True`, toggle if this trace should be displayed
3131
7. `'scale'`: default 1, object geometric scaling factor
3232
8. `'updatefunc'`: default `None`, updates the trace parameters when `show` is called. Used to generate dynamic traces.
3333

34-
The following example shows how a **Plotly** trace is constructed with `Mesh3d` and `Scatter3d`:
34+
The following example shows how a **generic** trace is constructed with `Mesh3d` and `Scatter3d`:
3535

3636
```{code-cell} ipython3
3737
import numpy as np
@@ -40,7 +40,7 @@ import magpylib as magpy
4040
# Mesh3d trace #########################
4141
4242
trace_mesh3d = {
43-
'backend': 'plotly',
43+
'backend': 'generic',
4444
'constructor': 'Mesh3d',
4545
'kwargs': {
4646
'x': (1, 0, -1, 0),
@@ -59,7 +59,7 @@ coll.style.model3d.add_trace(trace_mesh3d)
5959
6060
ts = np.linspace(0, 2*np.pi, 30)
6161
trace_scatter3d = {
62-
'backend': 'plotly',
62+
'backend': 'generic',
6363
'constructor': 'Scatter3d',
6464
'kwargs': {
6565
'x': np.cos(ts),
@@ -68,7 +68,7 @@ trace_scatter3d = {
6868
'mode': 'lines',
6969
}
7070
}
71-
dipole = magpy.misc.Dipole(moment=(0,0,1), style_label="'Scatter3d' trace")
71+
dipole = magpy.misc.Dipole(moment=(0,0,1), style_label="'Scatter3d' trace", style_size=6)
7272
dipole.style.model3d.add_trace(trace_scatter3d)
7373
7474
magpy.show(coll, dipole, backend='plotly')
@@ -94,7 +94,7 @@ trace3.kwargs['z'] = np.cos(ts)
9494
9595
dipole.style.model3d.add_trace(trace3)
9696
97-
dipole.show(dipole, backend='plotly')
97+
dipole.show(dipole, backend='matplotlib')
9898
```
9999

100100
**Matplotlib** plotting functions often use positional arguments for $(x,y,z)$ input, that are handed over from `args=(x,y,z)` in `trace`. The following examples show how to construct traces with `plot`, `plot_surface` and `plot_trisurf`:
@@ -160,20 +160,19 @@ trace_trisurf = {
160160
mobius = magpy.misc.CustomSource(style_model3d_showdefault=False, position=(3,0,0))
161161
mobius.style.model3d.add_trace(trace_trisurf)
162162
163-
magpy.show(magnet, ball, mobius)
163+
magpy.show(magnet, ball, mobius, zoom=2)
164164
```
165165

166166
## Pre-defined 3D models
167167

168-
Automatic trace generators are provided for several 3D models in `magpylib.graphics.model3d`. They can be used as follows,
168+
Automatic trace generators are provided for several basic 3D models in `magpylib.graphics.model3d`. If no backend is specified, it defaults back to `'generic'`. They can be used as follows,
169169

170170
```{code-cell} ipython3
171171
import magpylib as magpy
172172
from magpylib.graphics import model3d
173173
174174
# prism trace ###################################
175175
trace_prism = model3d.make_Prism(
176-
backend='plotly',
177176
base=6,
178177
diameter=2,
179178
height=1,
@@ -184,7 +183,6 @@ obj0.style.model3d.add_trace(trace_prism)
184183
185184
# pyramid trace #################################
186185
trace_pyramid = model3d.make_Pyramid(
187-
backend='plotly',
188186
base=30,
189187
diameter=2,
190188
height=1,
@@ -195,7 +193,6 @@ obj1.style.model3d.add_trace(trace_pyramid)
195193
196194
# cuboid trace ##################################
197195
trace_cuboid = model3d.make_Cuboid(
198-
backend='plotly',
199196
dimension=(2,2,2),
200197
position=(0,3,0),
201198
)
@@ -204,7 +201,6 @@ obj2.style.model3d.add_trace(trace_cuboid)
204201
205202
# cylinder segment trace ########################
206203
trace_cylinder_segment = model3d.make_CylinderSegment(
207-
backend='plotly',
208204
dimension=(1, 2, 1, 140, 220),
209205
position=(1,0,-3),
210206
)
@@ -213,7 +209,6 @@ obj3.style.model3d.add_trace(trace_cylinder_segment)
213209
214210
# ellipsoid trace ###############################
215211
trace_ellipsoid = model3d.make_Ellipsoid(
216-
backend='plotly',
217212
dimension=(2,2,2),
218213
position=(0,0,3),
219214
)
@@ -222,7 +217,6 @@ obj4.style.model3d.add_trace(trace_ellipsoid)
222217
223218
# arrow trace ###################################
224219
trace_arrow = model3d.make_Arrow(
225-
backend='plotly',
226220
base=30,
227221
diameter=0.6,
228222
height=2,
@@ -238,7 +232,7 @@ magpy.show(obj0, obj1, obj2, obj3, obj4, obj5, backend='plotly')
238232

239233
## Adding a CAD model
240234

241-
As shown in {ref}`examples-3d-models`, it is possible to attach custom 3D model representations to any Magpylib object. In the example below we show how a standard CAD model can be transformed into a Magpylib graphic trace, and displayed by both `matplotlib` and `plotly` backends.
235+
As shown in {ref}`examples-3d-models`, it is possible to attach custom 3D model representations to any Magpylib object. In the example below we show how a standard CAD model can be transformed into a generic Magpylib graphic trace, and displayed by both `matplotlib` and `plotly` backends.
242236

243237
```{note}
244238
The code below requires installation of the `numpy-stl` package.
@@ -251,18 +245,20 @@ import requests
251245
import numpy as np
252246
from stl import mesh # requires installation of numpy-stl
253247
import magpylib as magpy
248+
from matplotlib.colors import to_hex
254249
255250
256-
def get_stl_color(x):
257-
""" transform stl_mesh attr to plotly color"""
251+
def bin_color_to_hex(x):
252+
""" transform binary rgb into hex color"""
258253
sb = f"{x:015b}"[::-1]
259-
r = int(255 / 31 * int(sb[:5], base=2))
260-
g = int(255 / 31 * int(sb[5:10], base=2))
261-
b = int(255 / 31 * int(sb[10:15], base=2))
262-
return f"rgb({r},{g},{b})"
254+
r = int(sb[:5], base=2)/31
255+
g = int(sb[5:10], base=2)/31
256+
b = int(sb[10:15], base=2)/31
257+
return to_hex((r,g,b))
258+
263259
264260
265-
def trace_from_stl(stl_file, backend='matplotlib'):
261+
def trace_from_stl(stl_file):
266262
"""
267263
Generates a Magpylib 3D model trace dictionary from an *.stl file.
268264
backend: 'matplotlib' or 'plotly'
@@ -278,26 +274,14 @@ def trace_from_stl(stl_file, backend='matplotlib'):
278274
k = np.take(ixr, [3 * k + 2 for k in range(p)])
279275
x, y, z = vertices.T
280276
281-
# generate and return Magpylib traces
282-
if backend == 'matplotlib':
283-
triangles = np.array([i, j, k]).T
284-
trace = {
285-
'backend': 'matplotlib',
286-
'constructor': 'plot_trisurf',
287-
'args': (x, y, z),
288-
'kwargs': {'triangles': triangles},
289-
}
290-
elif backend == 'plotly':
291-
colors = stl_mesh.attr.flatten()
292-
facecolor = np.array([get_stl_color(c) for c in colors]).T
293-
trace = {
294-
'backend': 'plotly',
295-
'constructor': 'Mesh3d',
296-
'kwargs': dict(x=x, y=y, z=z, i=i, j=j, k=k, facecolor=facecolor),
297-
}
298-
else:
299-
raise ValueError("Backend must be one of ['matplotlib', 'plotly'].")
300-
277+
# generate and return a generic trace which can be translated into any backend
278+
colors = stl_mesh.attr.flatten()
279+
facecolor = np.array([bin_color_to_hex(c) for c in colors]).T
280+
trace = {
281+
'backend': 'generic',
282+
'constructor': 'mesh3d',
283+
'kwargs': dict(x=x, y=y, z=z, i=i, j=j, k=k, facecolor=facecolor),
284+
}
301285
return trace
302286
303287
@@ -311,21 +295,20 @@ with tempfile.TemporaryDirectory() as temp:
311295
f.write(response.content)
312296
313297
# create traces for both backends
314-
trace_mpl = trace_from_stl(fn, backend='matplotlib')
315-
trace_ply = trace_from_stl(fn, backend='plotly')
298+
trace = trace_from_stl(fn)
316299
317300
# create sensor and add CAD model
318301
sensor = magpy.Sensor(style_label='PG-SSO-3 package')
319-
sensor.style.model3d.add_trace(trace_mpl)
320-
sensor.style.model3d.add_trace(trace_ply)
302+
sensor.style.model3d.add_trace(trace)
321303
322304
# create magnet and sensor path
323305
magnet = magpy.magnet.Cylinder(magnetization=(0,0,100), dimension=(15,20))
324306
sensor.position = np.linspace((-15,0,8), (-15,0,-4), 21)
325-
sensor.rotate_from_angax(np.linspace(0, 200, 21), 'z', anchor=0, start=0)
307+
sensor.rotate_from_angax(np.linspace(0, 180, 21), 'z', anchor=0, start=0)
326308
327-
# display with both backends
328-
magpy.show(sensor, magnet, style_path_frames=5, style_magnetization_show=False)
329-
magpy.show(sensor, magnet, style_path_frames=5, backend="plotly")
309+
# display with matplotlib and plotly backends
310+
args = (sensor, magnet)
311+
kwargs = dict(style_path_frames=5)
312+
magpy.show(args, **kwargs, backend="matplotlib")
313+
magpy.show(args, **kwargs, backend="plotly")
330314
```
331-

docs/examples/examples_21_compound.md

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jupytext:
66
format_version: 0.13
77
jupytext_version: 1.13.7
88
kernelspec:
9-
display_name: Python 3 (ipykernel)
9+
display_name: Python 3
1010
language: python
1111
name: python3
1212
---
@@ -52,7 +52,7 @@ class MagnetRing(magpy.Collection):
5252
"""updates the MagnetRing instance"""
5353
self._cubes = cubes
5454
ring_radius = cubes/3
55-
55+
5656
# construct in temporary Collection for path transfer
5757
temp_coll = magpy.Collection()
5858
for i in range(cubes):
@@ -71,20 +71,18 @@ class MagnetRing(magpy.Collection):
7171
7272
# add parameter-dependent 3d trace
7373
self.style.model3d.data = []
74-
self.style.model3d.add_trace(self._custom_trace3d('plotly'))
75-
self.style.model3d.add_trace(self._custom_trace3d('matplotlib'))
74+
self.style.model3d.add_trace(self._custom_trace3d())
7675
7776
return self
7877
79-
def _custom_trace3d(self, backend):
78+
def _custom_trace3d(self):
8079
""" creates a parameter-dependent 3d model"""
8180
r1 = self.cubes/3 - .6
8281
r2 = self.cubes/3 + 0.6
8382
trace = magpy.graphics.model3d.make_CylinderSegment(
84-
backend=backend,
8583
dimension=(r1, r2, 1.1, 0, 360),
8684
vert=150,
87-
**{('opacity' if backend=='plotly' else 'alpha') :0.5}
85+
opacity=0.5,
8886
)
8987
return trace
9088
```
@@ -125,7 +123,6 @@ Custom traces can be computationally costly to construct. In the above example,
125123
To make our compounds ready for heavy computation, it is possible to provide a callable as a trace, which will only be constructed when `show` is called. The following modification of the above example demonstrates this:
126124

127125
```{code-cell} ipython3
128-
from functools import partial
129126
import magpylib as magpy
130127
import numpy as np
131128
@@ -143,8 +140,7 @@ class MagnetRingAdv(magpy.Collection):
143140
self._update(cubes)
144141
145142
# hand trace over as callable
146-
self.style.model3d.add_trace(partial(self._custom_trace3d, 'plotly'))
147-
self.style.model3d.add_trace(partial(self._custom_trace3d, 'matplotlib'))
143+
self.style.model3d.add_trace(self._custom_trace3d)
148144
149145
@property
150146
def cubes(self):
@@ -160,7 +156,7 @@ class MagnetRingAdv(magpy.Collection):
160156
"""updates the MagnetRing instance"""
161157
self._cubes = cubes
162158
ring_radius = cubes/3
163-
159+
164160
# construct in temporary Collection for path transfer
165161
temp_coll = magpy.Collection()
166162
for i in range(cubes):
@@ -179,26 +175,25 @@ class MagnetRingAdv(magpy.Collection):
179175
180176
return self
181177
182-
def _custom_trace3d(self, backend):
178+
def _custom_trace3d(self):
183179
""" creates a parameter-dependent 3d model"""
184180
r1 = self.cubes/3 - .6
185181
r2 = self.cubes/3 + 0.6
186182
trace = magpy.graphics.model3d.make_CylinderSegment(
187-
backend=backend,
188183
dimension=(r1, r2, 1.1, 0, 360),
189184
vert=150,
190-
**{('opacity' if backend=='plotly' else 'alpha') :0.5}
185+
opacity=0.5,
191186
)
192187
return trace
193188
```
194189

195190
All we have done is, to remove the trace construction from the `_update` method, and instead provide `_custom_trace3d` as callable in `__init__` with the help of `partial`.
196191

197192
```{code-cell} ipython3
198-
ring0 = MagnetRing()
193+
ring0 = MagnetRing()
199194
%time for _ in range(100): ring0.cubes=10
200195
201-
ring1 = MagnetRingAdv()
196+
ring1 = MagnetRingAdv()
202197
%time for _ in range(100): ring1.cubes=10
203198
```
204199

@@ -214,5 +209,3 @@ for i,cub in zip([2,7,12,17,22], [20,16,12,8,4]):
214209
215210
magpy.show(rings, animation=2, backend='plotly', style_path_show=False)
216211
```
217-
218-

magpylib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
]
5050

5151
# create interface to outside of package
52+
from magpylib._src.defaults.defaults_utility import SUPPORTED_PLOTTING_BACKENDS
5253
from magpylib import magnet, current, misc, core, graphics
5354
from magpylib._src.defaults.defaults_classes import default_settings as defaults
5455
from magpylib._src.fields import getB, getH

magpylib/_src/defaults/defaults_classes.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ class Display(MagicProperties):
5050
----------
5151
backend: str, default='matplotlib'
5252
Defines the plotting backend to be used by default, if not explicitly set in the `display`
53-
function. Can be one of `['matplotlib', 'plotly']`
53+
function (e.g. 'matplotlib', 'plotly').
54+
Supported backends are defined in magpylib.SUPPORTED_PLOTTING_BACKENDS
5455
5556
colorsequence: iterable, default=
5657
['#2E91E5', '#E15F99', '#1CA71C', '#FB0D0D', '#DA16FF', '#222A2A',
@@ -80,7 +81,8 @@ class Display(MagicProperties):
8081
@property
8182
def backend(self):
8283
"""plotting backend to be used by default, if not explicitly set in the `display`
83-
function. Can be one of `['matplotlib', 'plotly']`"""
84+
function (e.g. 'matplotlib', 'plotly').
85+
Supported backends are defined in magpylib.SUPPORTED_PLOTTING_BACKENDS"""
8486
return self._backend
8587

8688
@backend.setter

0 commit comments

Comments
 (0)