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

Skip to content

Commit 0f5f13e

Browse files
authored
Add units_length input to the show function (#786)
* draft * add zoom by subplots, fix axes labeling * fix empty subplots * fix subplot ranges and animation * fix zoom check * fix labeling * fix generic extra trace scaling * fix extra non generic scaling * pylint * add cm and dm as units_length inputs * fix ranges on non generic extra traces * fix edge case range where no trace in subplot * rework row cols conflicting inputs checks * update github actions * bump codeql actions versions * fix checkout * fix version * update * move to dev version * add tests * add docs entry * fix typo in test * pylint * refactor * fix autosize scaling with units * ad units_length=None als option to remove axes labels * fix axes label in parentheses * remove old code * refactor to allow units_length="auto" * fix same color for different suplots * remove unused variable (code ql) * update changelog * update docs * fix collection subobj not being displayed * fix collection precedence in show * fix colorsquence on same obj in different subplot * add color precedence tests * set units_length default to "auto" * fix line color matching 2D vs 3D * refactor * refactor * add special case marker color to 2d traces * refactor show_func * fix different output units in 2d subplots * pylint * fix extra trace rescaling * fix single to resize trace on different subplots * fix rescaling when vertices are None instead fo np.nan * fix pyvista streamlines example * fix collection model3d not being displayed * replace `np.NINF` with `-np.inf` * pylint * replace ´np.row_stack´ with `np.vstack` * update changelog * bump version --------- Co-authored-by: Boisselet Alexandre (IFAT DC ATV SC D TE2) <[email protected]>
1 parent fb88e48 commit 0f5f13e

15 files changed

+712
-350
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## [Unreleased] - YYYY-MM-DD
4+
- Added `units_length` input to the `show` function to allow displaying axes with different length units. This parameter can be set individually for each subplot. ([#786](https://github.com/magpylib/magpylib/pull/786))
5+
36
## [5.0.4] - 2024-06-18
47
- Add support for Numpy 2.0 ([#795](https://github.com/magpylib/magpylib/pull/789))
58
- Fix markers legend not being suppressible ([#795](https://github.com/magpylib/magpylib/pull/789))

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
</a>
2121
<a href="https://anaconda.org/conda-forge/magpylib"> <img src="https://anaconda.org/conda-forge/magpylib/badges/version.svg" alt="Conda Cloud" height="18">
2222
</a>
23-
<a href="https://mybinder.org/v2/gh/magpylib/magpylib/5.0.4?filepath=docs%2Fexamples"> <img src="https://mybinder.org/badge_logo.svg" alt="MyBinder link" height="18">
23+
<a href="https://mybinder.org/v2/gh/magpylib/magpylib/5.1.0dev?filepath=docs%2Fexamples"> <img src="https://mybinder.org/badge_logo.svg" alt="MyBinder link" height="18">
2424
</a>
2525
<a href="https://github.com/psf/black"> <img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="black" height="18">
2626
</a>
@@ -136,7 +136,7 @@ A valid software citation could be
136136
author = {{Michael-Ortner et al.}},
137137
title = {magpylib},
138138
url = {https://magpylib.readthedocs.io/en/latest/},
139-
version = {5.0.4},
139+
version = {5.1.0dev},
140140
date = {2023-06-25},
141141
}
142142
```

docs/_pages/user_guide/docs/docs_graphics.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ jupytext:
44
extension: .md
55
format_name: myst
66
format_version: 0.13
7-
jupytext_version: 1.16.0
7+
jupytext_version: 1.16.1
88
kernelspec:
9-
display_name: Python 3
9+
display_name: Python 3 (ipykernel)
1010
language: python
1111
name: python3
1212
orphan: true
@@ -422,3 +422,23 @@ with magpy.show_context(loop, sens, animation=True) as sc:
422422
sc.show(output=["Hx", "Hy", "Hz"], row=2)
423423
sc.show(output="Hxyz", col=2, row=2)
424424
```
425+
426+
### Canvas length units
427+
428+
When displaying very small Magpylib objects, the axes scaling in meters might be inadequate and you may want to use other units that fit the system dimensions more nicely. The example below shows how to display an object (in this case the same) with different length units and zoom levels.
429+
430+
```{tip}
431+
Setting `units_length="auto"` will infer the most suitable units based on the maximum range of the system.
432+
```
433+
434+
```{code-cell} ipython3
435+
import magpylib as magpy
436+
437+
c1 = magpy.magnet.Cuboid(dimension=(0.001, 0.001, 0.001), polarization=(1, 2, 3))
438+
439+
with magpy.show_context(c1, backend="matplotlib") as s:
440+
s.show(row=1, col=1, units_length="auto", zoom=0)
441+
s.show(row=1, col=2, units_length="mm", zoom=1)
442+
s.show(row=2, col=1, units_length="µm", zoom=2)
443+
s.show(row=2, col=2, units_length="m", zoom=3)
444+
```

docs/_pages/user_guide/examples/examples_vis_pv_streamlines.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ strl = grid.streamlines_from_source(
4848
# Create a Pyvista plotting scene
4949
pl = pv.Plotter()
5050
51-
# Add magnet to scene
52-
magpy.show(magnet, canvas=pl, backend="pyvista")
51+
# Add magnet to scene - streamlines units are assumed to be meters
52+
magpy.show(magnet, canvas=pl, units_length="m", backend="pyvista")
5353
5454
# Prepare legend parameters
5555
legend_args = {

magpylib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
2929
"""
3030
# module level dunders
31-
__version__ = "5.0.4"
31+
__version__ = "5.1.0dev"
3232
__author__ = "Michael Ortner & Alexandre Boisselet"
3333
__credits__ = "The Magpylib community"
3434
__all__ = [

magpylib/_src/display/backend_matplotlib.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ def display_matplotlib(
249249
"""Display objects and paths graphically using the matplotlib library."""
250250
frames = data["frames"]
251251
ranges = data["ranges"]
252+
labels = data["labels"]
252253

253254
fig_kwargs = {} if not fig_kwargs else fig_kwargs
254255
fig_kwargs = {"dpi": 80, **fig_kwargs}
@@ -352,10 +353,11 @@ def draw_frame(frame_ind):
352353
for row_col_num, ax in axes.items():
353354
count = count_with_labels.get(row_col_num, 0)
354355
if ax.name == "3d":
355-
ax.set(
356-
**{f"{k}label": f"{k} (m)" for k in "xyz"},
357-
**{f"{k}lim": r for k, r in zip("xyz", ranges)},
358-
)
356+
if row_col_num in ranges:
357+
ax.set(
358+
**{f"{k}label": labels[row_col_num][k] for k in "xyz"},
359+
**{f"{k}lim": r for k, r in zip("xyz", ranges[row_col_num])},
360+
)
359361
ax.set_box_aspect(aspect=(1, 1, 1))
360362
if 0 < count <= legend_maxitems:
361363
lg_kw = {"bbox_to_anchor": (1.04, 1), "loc": "upper left"}

magpylib/_src/display/backend_plotly.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,17 @@ def match_args(ttype: str):
7575
return set(named_args)
7676

7777

78-
def apply_fig_ranges(fig, ranges, apply2d=True):
78+
def apply_fig_ranges(fig, ranges_rc, labels_rc, apply2d=True):
7979
"""This is a helper function which applies the ranges properties of the provided `fig` object
80-
according to a provided ranges. All three space direction will be equal and match the
81-
maximum of the ranges needed to display all objects, including their paths.
80+
according to a provided ranges for each subplot. All three space direction will be equal and
81+
match the maximum of the ranges needed to display all objects, including their paths.
8282
8383
Parameters
8484
----------
85-
ranges: array of dimension=(3,2)
85+
ranges_rc: dict of arrays of dimension=(3,2)
8686
min and max graph range
87+
labels_rc: dict of dicts
88+
contains a dict with 'x', 'y', 'z' keys and respective labels as strings for each subplot
8789
8890
apply2d: bool, default = True
8991
applies fixed range also on 2d traces
@@ -92,15 +94,27 @@ def apply_fig_ranges(fig, ranges, apply2d=True):
9294
-------
9395
None: NoneType
9496
"""
95-
fig.update_scenes(
96-
**{
97-
f"{k}axis": {"range": ranges[i], "autorange": False, "title": f"{k} (m)"}
98-
for i, k in enumerate("xyz")
99-
},
100-
aspectratio={k: 1 for k in "xyz"},
101-
aspectmode="manual",
102-
camera_eye={"x": 1, "y": -1.5, "z": 1.4},
103-
)
97+
for rc, ranges in ranges_rc.items():
98+
row, col = rc
99+
labels = labels_rc.get(rc, {k: "" for k in "xyz"})
100+
kwargs = {
101+
**{
102+
f"{k}axis": {
103+
"range": ranges[i],
104+
"autorange": False,
105+
"title": labels[k],
106+
}
107+
for i, k in enumerate("xyz")
108+
},
109+
"aspectratio": {k: 1 for k in "xyz"},
110+
"aspectmode": "manual",
111+
"camera_eye": {"x": 1, "y": -1.5, "z": 1.4},
112+
}
113+
114+
# pylint: disable=protected-access
115+
if fig._grid_ref is not None:
116+
kwargs.update({"row": row, "col": col})
117+
fig.update_scenes(**kwargs)
104118
if apply2d:
105119
apply_2d_ranges(fig)
106120

@@ -274,7 +288,6 @@ def process_extra_trace(model):
274288

275289
def display_plotly(
276290
data,
277-
zoom=1,
278291
canvas=None,
279292
renderer=None,
280293
return_fig=False,
@@ -345,11 +358,13 @@ def display_plotly(
345358
rows=rows_list,
346359
cols=cols_list,
347360
)
348-
ranges = data["ranges"]
361+
ranges_rc = data["ranges"]
349362
if extra_data:
350-
ranges = get_scene_ranges(*frames[0]["data"], zoom=zoom)
363+
ranges_rc = get_scene_ranges(*frames[0]["data"])
351364
if update_layout:
352-
apply_fig_ranges(fig, ranges, apply2d=isanimation)
365+
apply_fig_ranges(
366+
fig, ranges_rc, labels_rc=data["labels"], apply2d=isanimation
367+
)
353368
fig.update_layout(
354369
legend_itemsizing="constant",
355370
# legend_groupclick="toggleitem",

magpylib/_src/display/display.py

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
from magpylib._src.defaults.defaults_utility import get_defaults_dict
1212
from magpylib._src.display.traces_generic import MagpyMarkers
1313
from magpylib._src.display.traces_generic import get_frames
14+
from magpylib._src.display.traces_utility import DEFAULT_ROW_COL_PARAMS
15+
from magpylib._src.display.traces_utility import linearize_dict
1416
from magpylib._src.display.traces_utility import process_show_input_objs
1517
from magpylib._src.input_checks import check_format_input_backend
1618
from magpylib._src.input_checks import check_format_input_vector
1719
from magpylib._src.input_checks import check_input_animation
18-
from magpylib._src.input_checks import check_input_zoom
1920
from magpylib._src.utility import check_path_format
2021

21-
disp_args = get_defaults_dict("display").keys()
22+
disp_args = set(get_defaults_dict("display"))
2223

2324

2425
class RegisteredBackend:
@@ -30,14 +31,14 @@ def __init__(
3031
self,
3132
*,
3233
name,
33-
show_func_getter,
34+
show_func,
3435
supports_animation,
3536
supports_subplots,
3637
supports_colorgradient,
3738
supports_animation_output,
3839
):
3940
self.name = name
40-
self.show_func_getter = show_func_getter
41+
self.show_func = show_func
4142
self.supports = {
4243
"animation": supports_animation,
4344
"subplots": supports_subplots,
@@ -54,7 +55,6 @@ def show(
5455
cls,
5556
*objs,
5657
backend,
57-
zoom=0,
5858
title=None,
5959
max_rows=None,
6060
max_cols=None,
@@ -83,12 +83,18 @@ def show(
8383
f"\nFalling back to: {params}"
8484
)
8585
kwargs.update(params)
86-
frame_kwargs = {
86+
display_kwargs = {
8787
k: v
8888
for k, v in kwargs.items()
89-
if any(k.startswith(arg) for arg in disp_args)
89+
if any(k.startswith(arg) for arg in disp_args - {"style"})
90+
}
91+
style_kwargs = {k: v for k, v in kwargs.items() if k.startswith("style")}
92+
style_kwargs = linearize_dict(style_kwargs, separator="_")
93+
kwargs = {
94+
k: v
95+
for k, v in kwargs.items()
96+
if (k not in display_kwargs and k not in style_kwargs)
9097
}
91-
kwargs = {k: v for k, v in kwargs.items() if k not in frame_kwargs}
9298
backend_kwargs = {
9399
k[len(backend) + 1 :]: v
94100
for k, v in kwargs.items()
@@ -117,13 +123,12 @@ def show(
117123
objs,
118124
supports_colorgradient=self.supports["colorgradient"],
119125
backend=backend,
120-
zoom=zoom,
121126
title=title,
122-
**frame_kwargs,
127+
style_kwargs=style_kwargs,
128+
**display_kwargs,
123129
)
124-
return self.show_func_getter()(
130+
return self.show_func(
125131
data,
126-
zoom=zoom,
127132
max_rows=max_rows,
128133
max_cols=max_cols,
129134
subplot_specs=subplot_specs,
@@ -136,12 +141,9 @@ def show(
136141
def get_show_func(backend):
137142
"""Return the backend show function"""
138143
# defer import to show call. Importerror should only fail if unavalaible backend is called
139-
return lambda: getattr(
144+
return lambda *args, backend=backend, **kwargs: getattr(
140145
import_module(f"magpylib._src.display.backend_{backend}"), f"display_{backend}"
141-
)
142-
143-
144-
ROW_COL_SPECIFIC_NAMES = ("row", "col", "output", "sumup", "pixel_agg", "in_out")
146+
)(*args, **kwargs)
145147

146148

147149
def infer_backend(canvas):
@@ -182,7 +184,6 @@ def _show(
182184
*objects,
183185
backend=None,
184186
animation=False,
185-
zoom=0,
186187
markers=None,
187188
**kwargs,
188189
):
@@ -193,9 +194,10 @@ def _show(
193194

194195
# process input objs
195196
objects, obj_list_flat, max_rows, max_cols, subplot_specs = process_show_input_objs(
196-
objects, **{k: v for k, v in kwargs.items() if k in ROW_COL_SPECIFIC_NAMES}
197+
objects,
198+
**{k: v for k, v in kwargs.items() if k in DEFAULT_ROW_COL_PARAMS},
197199
)
198-
kwargs = {k: v for k, v in kwargs.items() if k not in ROW_COL_SPECIFIC_NAMES}
200+
kwargs = {k: v for k, v in kwargs.items() if k not in DEFAULT_ROW_COL_PARAMS}
199201
kwargs["max_rows"], kwargs["max_cols"] = max_rows, max_cols
200202
kwargs["subplot_specs"] = subplot_specs
201203

@@ -204,7 +206,6 @@ def _show(
204206

205207
# input checks
206208
backend = check_format_input_backend(backend)
207-
check_input_zoom(zoom)
208209
check_input_animation(animation)
209210
check_format_input_vector(
210211
markers,
@@ -216,23 +217,14 @@ def _show(
216217
)
217218

218219
if markers:
219-
objects = [
220-
*objects,
221-
{
222-
"objects": [MagpyMarkers(*markers)],
223-
"row": 1,
224-
"col": 1,
225-
"output": "model3d",
226-
},
227-
]
220+
objects.append({"objects": [MagpyMarkers(*markers)], **DEFAULT_ROW_COL_PARAMS})
228221

229222
if backend == "auto":
230223
backend = infer_backend(kwargs.get("canvas", None))
231224

232225
return RegisteredBackend.show(
233226
backend=backend,
234227
*objects,
235-
zoom=zoom,
236228
animation=animation,
237229
**kwargs,
238230
)
@@ -407,9 +399,9 @@ def show(
407399
}
408400
)
409401
if ctx.isrunning:
410-
rco = {k: v for k, v in kwargs.items() if k in ROW_COL_SPECIFIC_NAMES}
402+
rco = {k: v for k, v in kwargs.items() if k in DEFAULT_ROW_COL_PARAMS}
411403
ctx.kwargs.update(
412-
{k: v for k, v in kwargs.items() if k not in ROW_COL_SPECIFIC_NAMES}
404+
{k: v for k, v in kwargs.items() if k not in DEFAULT_ROW_COL_PARAMS}
413405
)
414406
ctx_objects = tuple({**o, **rco} for o in ctx.objects_from_ctx)
415407
objects, *_ = process_show_input_objs(ctx_objects + objects, **rco)
@@ -452,11 +444,11 @@ def show_context(
452444
)
453445
try:
454446
ctx.isrunning = True
455-
rco = {k: v for k, v in kwargs.items() if k in ROW_COL_SPECIFIC_NAMES}
447+
rco = {k: v for k, v in kwargs.items() if k in DEFAULT_ROW_COL_PARAMS}
456448
objects, *_ = process_show_input_objs(objects, **rco)
457449
ctx.objects_from_ctx += tuple(objects)
458450
ctx.kwargs.update(
459-
{k: v for k, v in kwargs.items() if k not in ROW_COL_SPECIFIC_NAMES}
451+
{k: v for k, v in kwargs.items() if k not in DEFAULT_ROW_COL_PARAMS}
460452
)
461453
yield ctx
462454
ctx.show_return_value = _show(*ctx.objects, **ctx.kwargs)
@@ -491,7 +483,7 @@ def reset(self, reset_show_return_value=True):
491483

492484
RegisteredBackend(
493485
name="matplotlib",
494-
show_func_getter=get_show_func("matplotlib"),
486+
show_func=get_show_func("matplotlib"),
495487
supports_animation=True,
496488
supports_subplots=True,
497489
supports_colorgradient=False,
@@ -501,7 +493,7 @@ def reset(self, reset_show_return_value=True):
501493

502494
RegisteredBackend(
503495
name="plotly",
504-
show_func_getter=get_show_func("plotly"),
496+
show_func=get_show_func("plotly"),
505497
supports_animation=True,
506498
supports_subplots=True,
507499
supports_colorgradient=True,
@@ -510,7 +502,7 @@ def reset(self, reset_show_return_value=True):
510502

511503
RegisteredBackend(
512504
name="pyvista",
513-
show_func_getter=get_show_func("pyvista"),
505+
show_func=get_show_func("pyvista"),
514506
supports_animation=True,
515507
supports_subplots=True,
516508
supports_colorgradient=True,

0 commit comments

Comments
 (0)